A container registry that uses the AT Protocol for manifest storage and S3 for blob storage. atcr.io
docker container atproto go
73
fork

Configure Feed

Select the types of activity you want to include in your feed.

at test 728 lines 19 kB view raw view rendered
1# Development Workflow for ATCR 2 3## The Problem 4 5**Current development cycle with Docker:** 61. Edit CSS, JS, template, or Go file 72. Run `docker compose build` (rebuilds entire image) 83. Run `docker compose up` (restart container) 94. Wait **2-3 minutes** for changes to appear 105. Test, find issue, repeat... 11 12**Why it's slow:** 13- All assets embedded via `embed.FS` at compile time 14- Multi-stage Docker build compiles everything from scratch 15- No development mode exists 16- Final image uses `scratch` base (no tools, no hot reload) 17 18## The Solution 19 20**Development setup combining:** 211. **Dockerfile.devel** - Development-focused container (golang base, not scratch) 222. **Volume mounts** - Live code editing (changes appear instantly in container) 233. **DirFS** - Skip embed, read templates/CSS/JS from filesystem 244. **Air** - Auto-rebuild on Go code changes 25 26**Results:** 27- CSS/JS/Template changes: **Instant** (0 seconds, just refresh browser) 28- Go code changes: **2-5 seconds** (vs 2-3 minutes) 29- Production builds: **Unchanged** (still optimized with embed.FS) 30 31## How It Works 32 33### Architecture Flow 34 35``` 36┌─────────────────────────────────────────────────────┐ 37│ Your Editor (VSCode, etc) │ 38│ Edit: style.css, app.js, *.html, *.go files │ 39└─────────────────┬───────────────────────────────────┘ 40 │ (files saved to disk) 41 42┌─────────────────────────────────────────────────────┐ 43│ Volume Mount (docker-compose.dev.yml) │ 44│ volumes: │ 45│ - .:/app (entire codebase mounted) │ 46└─────────────────┬───────────────────────────────────┘ 47 │ (changes appear instantly in container) 48 49┌─────────────────────────────────────────────────────┐ 50│ Container (golang:1.25.2 base, has all tools) │ 51│ │ 52│ ┌──────────────────────────────────────┐ │ 53│ │ Air (hot reload tool) │ │ 54│ │ Watches: *.go, *.html, *.css, *.js │ │ 55│ │ │ │ 56│ │ On change: │ │ 57│ │ - *.go → rebuild binary (2-5s) │ │ 58│ │ - templates/css/js → restart only │ │ 59│ └──────────────────────────────────────┘ │ 60│ │ │ 61│ ▼ │ 62│ ┌──────────────────────────────────────┐ │ 63│ │ ATCR AppView (ATCR_DEV_MODE=true) │ │ 64│ │ │ │ 65│ │ ui.go checks DEV_MODE: │ │ 66│ │ if DEV_MODE: │ │ 67│ │ templatesFS = os.DirFS("...") │ │ 68│ │ staticFS = os.DirFS("...") │ │ 69│ │ else: │ │ 70│ │ use embed.FS (production) │ │ 71│ │ │ │ 72│ │ Result: Reads from mounted files │ │ 73│ └──────────────────────────────────────┘ │ 74└─────────────────────────────────────────────────────┘ 75``` 76 77### Change Scenarios 78 79#### Scenario 1: Edit CSS/JS/Templates 80``` 811. Edit pkg/appview/static/css/style.css in VSCode 822. Save file 833. Change appears in container via volume mount (instant) 844. App uses os.DirFS → reads new file from disk (instant) 855. Refresh browser → see changes 86``` 87**Time:** **Instant** (0 seconds) 88**No rebuild, no restart!** 89 90#### Scenario 2: Edit Go Code 91``` 921. Edit pkg/appview/handlers/home.go 932. Save file 943. Air detects .go file change 954. Air runs: go build -o ./tmp/atcr-appview ./cmd/appview 965. Air kills old process and starts new binary 976. App runs with new code 98``` 99**Time:** **2-5 seconds** 100**Fast incremental build!** 101 102## Implementation 103 104### Step 1: Create Dockerfile.devel 105 106Create `Dockerfile.devel` in project root: 107 108```dockerfile 109# Development Dockerfile with hot reload support 110FROM golang:1.25.2-trixie 111 112# Install Air for hot reload 113RUN go install github.com/cosmtrek/air@latest 114 115# Install SQLite (required for CGO in ATCR) 116RUN apt-get update && apt-get install -y \ 117 sqlite3 \ 118 libsqlite3-dev \ 119 && rm -rf /var/lib/apt/lists/* 120 121WORKDIR /app 122 123# Copy dependency files and download (cached layer) 124COPY go.mod go.sum ./ 125RUN go mod download 126 127# Note: Source code comes from volume mount 128# (no COPY . . needed - that's the whole point!) 129 130# Air will handle building and running 131CMD ["air", "-c", ".air.toml"] 132``` 133 134### Step 2: Create docker-compose.dev.yml 135 136Create `docker-compose.dev.yml` in project root: 137 138```yaml 139version: '3.8' 140 141services: 142 atcr-appview: 143 build: 144 context: . 145 dockerfile: Dockerfile.devel 146 volumes: 147 # Mount entire codebase (live editing) 148 - .:/app 149 # Cache Go modules (faster rebuilds) 150 - go-cache:/go/pkg/mod 151 # Persist SQLite database 152 - atcr-ui-dev:/var/lib/atcr 153 environment: 154 # Enable development mode (uses os.DirFS) 155 ATCR_DEV_MODE: "true" 156 157 # AppView configuration 158 ATCR_HTTP_ADDR: ":5000" 159 ATCR_BASE_URL: "http://localhost:5000" 160 ATCR_DEFAULT_HOLD_DID: "did:web:hold01.atcr.io" 161 162 # Database 163 ATCR_UI_DATABASE_PATH: "/var/lib/atcr/ui.db" 164 165 # Auth 166 ATCR_AUTH_KEY_PATH: "/var/lib/atcr/auth/private-key.pem" 167 168 # UI 169 ATCR_UI_ENABLED: "true" 170 171 # Jetstream (optional) 172 # JETSTREAM_URL: "wss://jetstream2.us-east.bsky.network/subscribe" 173 # ATCR_BACKFILL_ENABLED: "false" 174 ports: 175 - "5000:5000" 176 networks: 177 - atcr-dev 178 179 # Add other services as needed (postgres, hold, etc) 180 # atcr-hold: 181 # ... 182 183networks: 184 atcr-dev: 185 driver: bridge 186 187volumes: 188 go-cache: 189 atcr-ui-dev: 190``` 191 192### Step 3: Create .air.toml 193 194Create `.air.toml` in project root: 195 196```toml 197# Air configuration for hot reload 198# https://github.com/cosmtrek/air 199 200root = "." 201testdata_dir = "testdata" 202tmp_dir = "tmp" 203 204[build] 205 # Arguments to pass to binary (AppView needs "serve") 206 args_bin = ["serve"] 207 208 # Where to output the built binary 209 bin = "./tmp/atcr-appview" 210 211 # Build command 212 cmd = "go build -o ./tmp/atcr-appview ./cmd/appview" 213 214 # Delay before rebuilding (ms) - debounce rapid saves 215 delay = 1000 216 217 # Directories to exclude from watching 218 exclude_dir = [ 219 "tmp", 220 "vendor", 221 "bin", 222 ".git", 223 "node_modules", 224 "testdata" 225 ] 226 227 # Files to exclude from watching 228 exclude_file = [] 229 230 # Regex patterns to exclude 231 exclude_regex = ["_test\\.go"] 232 233 # Don't rebuild if file content unchanged 234 exclude_unchanged = false 235 236 # Follow symlinks 237 follow_symlink = false 238 239 # Full command to run (leave empty to use cmd + bin) 240 full_bin = "" 241 242 # Directories to include (empty = all) 243 include_dir = [] 244 245 # File extensions to watch 246 include_ext = ["go", "html", "css", "js"] 247 248 # Specific files to watch 249 include_file = [] 250 251 # Delay before killing old process (s) 252 kill_delay = "0s" 253 254 # Log file for build errors 255 log = "build-errors.log" 256 257 # Use polling instead of fsnotify (for Docker/VM) 258 poll = false 259 poll_interval = 0 260 261 # Rerun binary if it exits 262 rerun = false 263 rerun_delay = 500 264 265 # Send interrupt signal instead of kill 266 send_interrupt = false 267 268 # Stop on build error 269 stop_on_error = false 270 271[color] 272 # Colorize output 273 app = "" 274 build = "yellow" 275 main = "magenta" 276 runner = "green" 277 watcher = "cyan" 278 279[log] 280 # Show only app logs (not build logs) 281 main_only = false 282 283 # Add timestamp to logs 284 time = false 285 286[misc] 287 # Clean tmp directory on exit 288 clean_on_exit = false 289 290[screen] 291 # Clear screen on rebuild 292 clear_on_rebuild = false 293 294 # Keep scrollback 295 keep_scroll = true 296``` 297 298### Step 4: Modify pkg/appview/ui.go 299 300Add conditional filesystem loading to `pkg/appview/ui.go`: 301 302```go 303package appview 304 305import ( 306 "embed" 307 "html/template" 308 "io/fs" 309 "log" 310 "net/http" 311 "os" 312) 313 314// Embedded assets (used in production) 315//go:embed templates/**/*.html 316var embeddedTemplatesFS embed.FS 317 318//go:embed static 319var embeddedStaticFS embed.FS 320 321// Actual filesystems used at runtime (conditional) 322var templatesFS fs.FS 323var staticFS fs.FS 324 325func init() { 326 // Development mode: read from filesystem for instant updates 327 if os.Getenv("ATCR_DEV_MODE") == "true" { 328 log.Println("🔧 DEV MODE: Using filesystem for templates and static assets") 329 templatesFS = os.DirFS("pkg/appview/templates") 330 staticFS = os.DirFS("pkg/appview/static") 331 } else { 332 // Production mode: use embedded assets 333 log.Println("📦 PRODUCTION MODE: Using embedded assets") 334 templatesFS = embeddedTemplatesFS 335 staticFS = embeddedStaticFS 336 } 337} 338 339// Templates returns parsed HTML templates 340func Templates() *template.Template { 341 tmpl, err := template.ParseFS(templatesFS, "templates/**/*.html") 342 if err != nil { 343 log.Fatalf("Failed to parse templates: %v", err) 344 } 345 return tmpl 346} 347 348// StaticHandler returns a handler for static files 349func StaticHandler() http.Handler { 350 sub, err := fs.Sub(staticFS, "static") 351 if err != nil { 352 log.Fatalf("Failed to create static sub-filesystem: %v", err) 353 } 354 return http.FileServer(http.FS(sub)) 355} 356``` 357 358**Important:** Update the `Templates()` function to NOT cache templates in dev mode: 359 360```go 361// Templates returns parsed HTML templates 362func Templates() *template.Template { 363 // In dev mode, reparse templates on every request (instant updates) 364 // In production, this could be cached 365 tmpl, err := template.ParseFS(templatesFS, "templates/**/*.html") 366 if err != nil { 367 log.Fatalf("Failed to parse templates: %v", err) 368 } 369 return tmpl 370} 371``` 372 373If you're caching templates, wrap it with a dev mode check: 374 375```go 376var templateCache *template.Template 377 378func Templates() *template.Template { 379 // Development: reparse every time (instant updates) 380 if os.Getenv("ATCR_DEV_MODE") == "true" { 381 tmpl, err := template.ParseFS(templatesFS, "templates/**/*.html") 382 if err != nil { 383 log.Printf("Template parse error: %v", err) 384 return template.New("error") 385 } 386 return tmpl 387 } 388 389 // Production: use cached templates 390 if templateCache == nil { 391 tmpl, err := template.ParseFS(templatesFS, "templates/**/*.html") 392 if err != nil { 393 log.Fatalf("Failed to parse templates: %v", err) 394 } 395 templateCache = tmpl 396 } 397 return templateCache 398} 399``` 400 401### Step 5: Add to .gitignore 402 403Add Air's temporary directory to `.gitignore`: 404 405``` 406# Air hot reload 407tmp/ 408build-errors.log 409``` 410 411## Usage 412 413### Starting Development Environment 414 415```bash 416# Build and start dev container 417docker compose -f docker-compose.dev.yml up --build 418 419# Or run in background 420docker compose -f docker-compose.dev.yml up -d 421 422# View logs 423docker compose -f docker-compose.dev.yml logs -f atcr-appview 424``` 425 426You should see Air starting: 427 428``` 429atcr-appview | 🔧 DEV MODE: Using filesystem for templates and static assets 430atcr-appview | 431atcr-appview | __ _ ___ 432atcr-appview | / /\ | | | |_) 433atcr-appview | /_/--\ |_| |_| \_ , built with Go 434atcr-appview | 435atcr-appview | watching . 436atcr-appview | !exclude tmp 437atcr-appview | building... 438atcr-appview | running... 439``` 440 441### Development Workflow 442 443#### 1. Edit Templates/CSS/JS (Instant Updates) 444 445```bash 446# Edit any template, CSS, or JS file 447vim pkg/appview/templates/pages/home.html 448vim pkg/appview/static/css/style.css 449vim pkg/appview/static/js/app.js 450 451# Save file → changes appear instantly 452# Just refresh browser (Cmd+R / Ctrl+R) 453``` 454 455**No rebuild, no restart!** Air might restart the app, but it's instant since no compilation is needed. 456 457#### 2. Edit Go Code (Fast Rebuild) 458 459```bash 460# Edit any Go file 461vim pkg/appview/handlers/home.go 462 463# Save file → Air detects change 464# Air output shows: 465# building... 466# build successful in 2.3s 467# restarting... 468 469# Refresh browser to see changes 470``` 471 472**2-5 second rebuild** instead of 2-3 minutes! 473 474### Stopping Development Environment 475 476```bash 477# Stop containers 478docker compose -f docker-compose.dev.yml down 479 480# Stop and remove volumes (fresh start) 481docker compose -f docker-compose.dev.yml down -v 482``` 483 484## Production Builds 485 486**Production builds are completely unchanged:** 487 488```bash 489# Production uses normal Dockerfile (embed.FS, scratch base) 490docker compose build 491 492# Or specific service 493docker compose build atcr-appview 494 495# Run production 496docker compose up 497``` 498 499**Why it works:** 500- Production doesn't set `ATCR_DEV_MODE=true` 501- `ui.go` defaults to embedded assets when env var is unset 502- Production Dockerfile still uses multi-stage build to scratch 503- No development dependencies in production image 504 505## Comparison 506 507| Change Type | Before (docker compose) | After (dev setup) | Improvement | 508|-------------|------------------------|-------------------|-------------| 509| Edit CSS | 2-3 minutes | **Instant (0s)** | ♾️x faster | 510| Edit JS | 2-3 minutes | **Instant (0s)** | ♾️x faster | 511| Edit Template | 2-3 minutes | **Instant (0s)** | ♾️x faster | 512| Edit Go Code | 2-3 minutes | **2-5 seconds** | 24-90x faster | 513| Production Build | Same | **Same** | No change | 514 515## Advanced: Local Development (No Docker) 516 517For even faster development, run locally without Docker: 518 519```bash 520# Set environment variables 521export ATCR_DEV_MODE=true 522export ATCR_HTTP_ADDR=:5000 523export ATCR_BASE_URL=http://localhost:5000 524export ATCR_DEFAULT_HOLD_DID=did:web:hold01.atcr.io 525export ATCR_UI_DATABASE_PATH=/tmp/atcr-ui.db 526export ATCR_AUTH_KEY_PATH=/tmp/atcr-auth-key.pem 527export ATCR_UI_ENABLED=true 528 529# Or use .env file 530source .env.appview 531 532# Run with Air 533air -c .air.toml 534 535# Or run directly (no hot reload) 536go run ./cmd/appview serve 537``` 538 539**Advantages:** 540- Even faster (no Docker overhead) 541- Native debugging with delve 542- Direct filesystem access 543- Full IDE integration 544 545**Disadvantages:** 546- Need to manage dependencies locally (SQLite, etc) 547- May differ from production environment 548 549## Troubleshooting 550 551### Air Not Rebuilding 552 553**Problem:** Air doesn't detect changes 554 555**Solution:** 556```bash 557# Check if Air is actually running 558docker compose -f docker-compose.dev.yml logs atcr-appview 559 560# Check .air.toml include_ext includes your file type 561# Default: ["go", "html", "css", "js"] 562 563# Restart container 564docker compose -f docker-compose.dev.yml restart atcr-appview 565``` 566 567### Templates Not Updating 568 569**Problem:** Template changes don't appear 570 571**Solution:** 572```bash 573# Check ATCR_DEV_MODE is set 574docker compose -f docker-compose.dev.yml exec atcr-appview env | grep DEV_MODE 575 576# Should output: ATCR_DEV_MODE=true 577 578# Check templates aren't cached (see Step 4 above) 579# Templates() should reparse in dev mode 580``` 581 582### Go Build Failing 583 584**Problem:** Air shows build errors 585 586**Solution:** 587```bash 588# Check build logs 589docker compose -f docker-compose.dev.yml logs atcr-appview 590 591# Or check build-errors.log in container 592docker compose -f docker-compose.dev.yml exec atcr-appview cat build-errors.log 593 594# Fix the Go error, save file, Air will retry 595``` 596 597### Volume Mount Not Working 598 599**Problem:** Changes don't appear in container 600 601**Solution:** 602```bash 603# Verify volume mount 604docker compose -f docker-compose.dev.yml exec atcr-appview ls -la /app 605 606# Should show your source files 607 608# On Windows/Mac, check Docker Desktop file sharing settings 609# Settings → Resources → File Sharing → add project directory 610``` 611 612### Permission Errors 613 614**Problem:** Cannot write to /var/lib/atcr 615 616**Solution:** 617```bash 618# In Dockerfile.devel, add: 619RUN mkdir -p /var/lib/atcr && chmod 777 /var/lib/atcr 620 621# Or use named volumes (already in docker-compose.dev.yml) 622volumes: 623 - atcr-ui-dev:/var/lib/atcr 624``` 625 626### Slow Builds Even with Air 627 628**Problem:** Air rebuilds slowly 629 630**Solution:** 631```bash 632# Use Go module cache volume (already in docker-compose.dev.yml) 633volumes: 634 - go-cache:/go/pkg/mod 635 636# Increase Air delay to debounce rapid saves 637# In .air.toml: 638delay = 2000 # 2 seconds 639 640# Or check if CGO is slowing builds 641# AppView needs CGO for SQLite, but you can try: 642CGO_ENABLED=0 go build # (won't work for ATCR, but good to know) 643``` 644 645## Tips & Tricks 646 647### Browser Auto-Reload (LiveReload) 648 649Add LiveReload for automatic browser refresh: 650 651```bash 652# Install browser extension 653# Chrome: https://chrome.google.com/webstore/detail/livereload 654# Firefox: https://addons.mozilla.org/en-US/firefox/addon/livereload-web-extension/ 655 656# Add livereload to .air.toml (future Air feature) 657# Or use a separate tool like browsersync 658``` 659 660### Database Resets 661 662Development database is in a named volume: 663 664```bash 665# Reset database (fresh start) 666docker compose -f docker-compose.dev.yml down -v 667docker compose -f docker-compose.dev.yml up 668 669# Or delete specific volume 670docker volume rm atcr_atcr-ui-dev 671``` 672 673### Multiple Environments 674 675Run dev and production side-by-side: 676 677```bash 678# Development on port 5000 679docker compose -f docker-compose.dev.yml up -d 680 681# Production on port 5001 682docker compose up -d 683 684# Now you can compare behavior 685``` 686 687### Debugging with Delve 688 689Add delve to Dockerfile.devel: 690 691```dockerfile 692RUN go install github.com/go-delve/delve/cmd/dlv@latest 693 694# Change CMD to use delve 695CMD ["dlv", "debug", "./cmd/appview", "--headless", "--listen=:2345", "--api-version=2", "--accept-multiclient", "--", "serve"] 696``` 697 698Then connect with VSCode or GoLand. 699 700## Summary 701 702**Development Setup (One-Time):** 7031. Create `Dockerfile.devel` 7042. Create `docker-compose.dev.yml` 7053. Create `.air.toml` 7064. Modify `pkg/appview/ui.go` for conditional DirFS 7075. Add `tmp/` to `.gitignore` 708 709**Daily Development:** 710```bash 711# Start 712docker compose -f docker-compose.dev.yml up 713 714# Edit files in your editor 715# Changes appear instantly (CSS/JS/templates) 716# Or in 2-5 seconds (Go code) 717 718# Stop 719docker compose -f docker-compose.dev.yml down 720``` 721 722**Production (Unchanged):** 723```bash 724docker compose build 725docker compose up 726``` 727 728**Result:** 100x faster development iteration! 🚀