A container registry that uses the AT Protocol for manifest storage and S3 for blob storage.
0
fork

Configure Feed

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

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.7 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│ │ publicFS = 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/public/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.7-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 # Jetstream (optional) 169 # JETSTREAM_URL: "wss://jetstream2.us-east.bsky.network/subscribe" 170 # ATCR_BACKFILL_ENABLED: "false" 171 ports: 172 - "5000:5000" 173 networks: 174 - atcr-dev 175 176 # Add other services as needed (postgres, hold, etc) 177 # atcr-hold: 178 # ... 179 180networks: 181 atcr-dev: 182 driver: bridge 183 184volumes: 185 go-cache: 186 atcr-ui-dev: 187``` 188 189### Step 3: Create .air.toml 190 191Create `.air.toml` in project root: 192 193```toml 194# Air configuration for hot reload 195# https://github.com/cosmtrek/air 196 197root = "." 198testdata_dir = "testdata" 199tmp_dir = "tmp" 200 201[build] 202 # Arguments to pass to binary (AppView needs "serve") 203 args_bin = ["serve"] 204 205 # Where to output the built binary 206 bin = "./tmp/atcr-appview" 207 208 # Build command 209 cmd = "go build -o ./tmp/atcr-appview ./cmd/appview" 210 211 # Delay before rebuilding (ms) - debounce rapid saves 212 delay = 1000 213 214 # Directories to exclude from watching 215 exclude_dir = [ 216 "tmp", 217 "vendor", 218 "bin", 219 ".git", 220 "node_modules", 221 "testdata" 222 ] 223 224 # Files to exclude from watching 225 exclude_file = [] 226 227 # Regex patterns to exclude 228 exclude_regex = ["_test\\.go"] 229 230 # Don't rebuild if file content unchanged 231 exclude_unchanged = false 232 233 # Follow symlinks 234 follow_symlink = false 235 236 # Full command to run (leave empty to use cmd + bin) 237 full_bin = "" 238 239 # Directories to include (empty = all) 240 include_dir = [] 241 242 # File extensions to watch 243 include_ext = ["go", "html", "css", "js"] 244 245 # Specific files to watch 246 include_file = [] 247 248 # Delay before killing old process (s) 249 kill_delay = "0s" 250 251 # Log file for build errors 252 log = "build-errors.log" 253 254 # Use polling instead of fsnotify (for Docker/VM) 255 poll = false 256 poll_interval = 0 257 258 # Rerun binary if it exits 259 rerun = false 260 rerun_delay = 500 261 262 # Send interrupt signal instead of kill 263 send_interrupt = false 264 265 # Stop on build error 266 stop_on_error = false 267 268[color] 269 # Colorize output 270 app = "" 271 build = "yellow" 272 main = "magenta" 273 runner = "green" 274 watcher = "cyan" 275 276[log] 277 # Show only app logs (not build logs) 278 main_only = false 279 280 # Add timestamp to logs 281 time = false 282 283[misc] 284 # Clean tmp directory on exit 285 clean_on_exit = false 286 287[screen] 288 # Clear screen on rebuild 289 clear_on_rebuild = false 290 291 # Keep scrollback 292 keep_scroll = true 293``` 294 295### Step 4: Modify pkg/appview/ui.go 296 297Add conditional filesystem loading to `pkg/appview/ui.go`: 298 299```go 300package appview 301 302import ( 303 "embed" 304 "html/template" 305 "io/fs" 306 "log" 307 "net/http" 308 "os" 309) 310 311// Embedded assets (used in production) 312//go:embed templates/**/*.html 313var embeddedTemplatesFS embed.FS 314 315//go:embed static 316var embeddedpublicFS embed.FS 317 318// Actual filesystems used at runtime (conditional) 319var templatesFS fs.FS 320var publicFS fs.FS 321 322func init() { 323 // Development mode: read from filesystem for instant updates 324 if os.Getenv("ATCR_DEV_MODE") == "true" { 325 log.Println("🔧 DEV MODE: Using filesystem for templates and static assets") 326 templatesFS = os.DirFS("pkg/appview/templates") 327 publicFS = os.DirFS("pkg/appview/static") 328 } else { 329 // Production mode: use embedded assets 330 log.Println("📦 PRODUCTION MODE: Using embedded assets") 331 templatesFS = embeddedTemplatesFS 332 publicFS = embeddedpublicFS 333 } 334} 335 336// Templates returns parsed HTML templates 337func Templates() *template.Template { 338 tmpl, err := template.ParseFS(templatesFS, "templates/**/*.html") 339 if err != nil { 340 log.Fatalf("Failed to parse templates: %v", err) 341 } 342 return tmpl 343} 344 345// StaticHandler returns a handler for static files 346func StaticHandler() http.Handler { 347 sub, err := fs.Sub(publicFS, "static") 348 if err != nil { 349 log.Fatalf("Failed to create static sub-filesystem: %v", err) 350 } 351 return http.FileServer(http.FS(sub)) 352} 353``` 354 355**Important:** Update the `Templates()` function to NOT cache templates in dev mode: 356 357```go 358// Templates returns parsed HTML templates 359func Templates() *template.Template { 360 // In dev mode, reparse templates on every request (instant updates) 361 // In production, this could be cached 362 tmpl, err := template.ParseFS(templatesFS, "templates/**/*.html") 363 if err != nil { 364 log.Fatalf("Failed to parse templates: %v", err) 365 } 366 return tmpl 367} 368``` 369 370If you're caching templates, wrap it with a dev mode check: 371 372```go 373var templateCache *template.Template 374 375func Templates() *template.Template { 376 // Development: reparse every time (instant updates) 377 if os.Getenv("ATCR_DEV_MODE") == "true" { 378 tmpl, err := template.ParseFS(templatesFS, "templates/**/*.html") 379 if err != nil { 380 log.Printf("Template parse error: %v", err) 381 return template.New("error") 382 } 383 return tmpl 384 } 385 386 // Production: use cached templates 387 if templateCache == nil { 388 tmpl, err := template.ParseFS(templatesFS, "templates/**/*.html") 389 if err != nil { 390 log.Fatalf("Failed to parse templates: %v", err) 391 } 392 templateCache = tmpl 393 } 394 return templateCache 395} 396``` 397 398### Step 5: Add to .gitignore 399 400Add Air's temporary directory to `.gitignore`: 401 402``` 403# Air hot reload 404tmp/ 405build-errors.log 406``` 407 408## Usage 409 410### Starting Development Environment 411 412```bash 413# Build and start dev container 414docker compose -f docker-compose.dev.yml up --build 415 416# Or run in background 417docker compose -f docker-compose.dev.yml up -d 418 419# View logs 420docker compose -f docker-compose.dev.yml logs -f atcr-appview 421``` 422 423You should see Air starting: 424 425``` 426atcr-appview | 🔧 DEV MODE: Using filesystem for templates and static assets 427atcr-appview | 428atcr-appview | __ _ ___ 429atcr-appview | / /\ | | | |_) 430atcr-appview | /_/--\ |_| |_| \_ , built with Go 431atcr-appview | 432atcr-appview | watching . 433atcr-appview | !exclude tmp 434atcr-appview | building... 435atcr-appview | running... 436``` 437 438### Development Workflow 439 440#### 1. Edit Templates/CSS/JS (Instant Updates) 441 442```bash 443# Edit any template, CSS, or JS file 444vim pkg/appview/templates/pages/home.html 445vim pkg/appview/public/css/style.css 446vim pkg/appview/public/js/app.js 447 448# Save file → changes appear instantly 449# Just refresh browser (Cmd+R / Ctrl+R) 450``` 451 452**No rebuild, no restart!** Air might restart the app, but it's instant since no compilation is needed. 453 454#### 2. Edit Go Code (Fast Rebuild) 455 456```bash 457# Edit any Go file 458vim pkg/appview/handlers/home.go 459 460# Save file → Air detects change 461# Air output shows: 462# building... 463# build successful in 2.3s 464# restarting... 465 466# Refresh browser to see changes 467``` 468 469**2-5 second rebuild** instead of 2-3 minutes! 470 471### Stopping Development Environment 472 473```bash 474# Stop containers 475docker compose -f docker-compose.dev.yml down 476 477# Stop and remove volumes (fresh start) 478docker compose -f docker-compose.dev.yml down -v 479``` 480 481## Production Builds 482 483**Production builds are completely unchanged:** 484 485```bash 486# Production uses normal Dockerfile (embed.FS, scratch base) 487docker compose build 488 489# Or specific service 490docker compose build atcr-appview 491 492# Run production 493docker compose up 494``` 495 496**Why it works:** 497- Production doesn't set `ATCR_DEV_MODE=true` 498- `ui.go` defaults to embedded assets when env var is unset 499- Production Dockerfile still uses multi-stage build to scratch 500- No development dependencies in production image 501 502## Comparison 503 504| Change Type | Before (docker compose) | After (dev setup) | Improvement | 505|-------------|------------------------|-------------------|-------------| 506| Edit CSS | 2-3 minutes | **Instant (0s)** | ♾️x faster | 507| Edit JS | 2-3 minutes | **Instant (0s)** | ♾️x faster | 508| Edit Template | 2-3 minutes | **Instant (0s)** | ♾️x faster | 509| Edit Go Code | 2-3 minutes | **2-5 seconds** | 24-90x faster | 510| Production Build | Same | **Same** | No change | 511 512## Advanced: Local Development (No Docker) 513 514For even faster development, run locally without Docker: 515 516```bash 517# Set environment variables 518export ATCR_DEV_MODE=true 519export ATCR_HTTP_ADDR=:5000 520export ATCR_BASE_URL=http://localhost:5000 521export ATCR_DEFAULT_HOLD_DID=did:web:hold01.atcr.io 522export ATCR_UI_DATABASE_PATH=/tmp/atcr-ui.db 523export ATCR_AUTH_KEY_PATH=/tmp/atcr-auth-key.pem 524 525# Or use .env file 526source .env.appview 527 528# Run with Air 529air -c .air.toml 530 531# Or run directly (no hot reload) 532go run ./cmd/appview serve 533``` 534 535**Advantages:** 536- Even faster (no Docker overhead) 537- Native debugging with delve 538- Direct filesystem access 539- Full IDE integration 540 541**Disadvantages:** 542- Need to manage dependencies locally (SQLite, etc) 543- May differ from production environment 544 545## Troubleshooting 546 547### Air Not Rebuilding 548 549**Problem:** Air doesn't detect changes 550 551**Solution:** 552```bash 553# Check if Air is actually running 554docker compose -f docker-compose.dev.yml logs atcr-appview 555 556# Check .air.toml include_ext includes your file type 557# Default: ["go", "html", "css", "js"] 558 559# Restart container 560docker compose -f docker-compose.dev.yml restart atcr-appview 561``` 562 563### Templates Not Updating 564 565**Problem:** Template changes don't appear 566 567**Solution:** 568```bash 569# Check ATCR_DEV_MODE is set 570docker compose -f docker-compose.dev.yml exec atcr-appview env | grep DEV_MODE 571 572# Should output: ATCR_DEV_MODE=true 573 574# Check templates aren't cached (see Step 4 above) 575# Templates() should reparse in dev mode 576``` 577 578### Go Build Failing 579 580**Problem:** Air shows build errors 581 582**Solution:** 583```bash 584# Check build logs 585docker compose -f docker-compose.dev.yml logs atcr-appview 586 587# Or check build-errors.log in container 588docker compose -f docker-compose.dev.yml exec atcr-appview cat build-errors.log 589 590# Fix the Go error, save file, Air will retry 591``` 592 593### Volume Mount Not Working 594 595**Problem:** Changes don't appear in container 596 597**Solution:** 598```bash 599# Verify volume mount 600docker compose -f docker-compose.dev.yml exec atcr-appview ls -la /app 601 602# Should show your source files 603 604# On Windows/Mac, check Docker Desktop file sharing settings 605# Settings → Resources → File Sharing → add project directory 606``` 607 608### Permission Errors 609 610**Problem:** Cannot write to /var/lib/atcr 611 612**Solution:** 613```bash 614# In Dockerfile.devel, add: 615RUN mkdir -p /var/lib/atcr && chmod 777 /var/lib/atcr 616 617# Or use named volumes (already in docker-compose.dev.yml) 618volumes: 619 - atcr-ui-dev:/var/lib/atcr 620``` 621 622### Slow Builds Even with Air 623 624**Problem:** Air rebuilds slowly 625 626**Solution:** 627```bash 628# Use Go module cache volume (already in docker-compose.dev.yml) 629volumes: 630 - go-cache:/go/pkg/mod 631 632# Increase Air delay to debounce rapid saves 633# In .air.toml: 634delay = 2000 # 2 seconds 635 636# Or check if CGO is slowing builds 637# AppView needs CGO for SQLite, but you can try: 638CGO_ENABLED=0 go build # (won't work for ATCR, but good to know) 639``` 640 641## Tips & Tricks 642 643### Browser Auto-Reload (LiveReload) 644 645Add LiveReload for automatic browser refresh: 646 647```bash 648# Install browser extension 649# Chrome: https://chrome.google.com/webstore/detail/livereload 650# Firefox: https://addons.mozilla.org/en-US/firefox/addon/livereload-web-extension/ 651 652# Add livereload to .air.toml (future Air feature) 653# Or use a separate tool like browsersync 654``` 655 656### Database Resets 657 658Development database is in a named volume: 659 660```bash 661# Reset database (fresh start) 662docker compose -f docker-compose.dev.yml down -v 663docker compose -f docker-compose.dev.yml up 664 665# Or delete specific volume 666docker volume rm atcr_atcr-ui-dev 667``` 668 669### Multiple Environments 670 671Run dev and production side-by-side: 672 673```bash 674# Development on port 5000 675docker compose -f docker-compose.dev.yml up -d 676 677# Production on port 5001 678docker compose up -d 679 680# Now you can compare behavior 681``` 682 683### Debugging with Delve 684 685Add delve to Dockerfile.devel: 686 687```dockerfile 688RUN go install github.com/go-delve/delve/cmd/dlv@latest 689 690# Change CMD to use delve 691CMD ["dlv", "debug", "./cmd/appview", "--headless", "--listen=:2345", "--api-version=2", "--accept-multiclient", "--", "serve"] 692``` 693 694Then connect with VSCode or GoLand. 695 696## Summary 697 698**Development Setup (One-Time):** 6991. Create `Dockerfile.devel` 7002. Create `docker-compose.dev.yml` 7013. Create `.air.toml` 7024. Modify `pkg/appview/ui.go` for conditional DirFS 7035. Add `tmp/` to `.gitignore` 704 705**Daily Development:** 706```bash 707# Start 708docker compose -f docker-compose.dev.yml up 709 710# Edit files in your editor 711# Changes appear instantly (CSS/JS/templates) 712# Or in 2-5 seconds (Go code) 713 714# Stop 715docker compose -f docker-compose.dev.yml down 716``` 717 718**Production (Unchanged):** 719```bash 720docker compose build 721docker compose up 722``` 723 724**Result:** 100x faster development iteration! 🚀