# Source: https://oneuptime.com/blog/post/2026-01-07-go-hot-reloading-docker-air/view#step-4-set-up-docker-compose # docker-compose.yml # Development environment with hot reloading services: # Main Go application with Air hot reloading keepnote-debug: extra_hosts: - "host.docker.internal:host-gateway" image: keepnote-debug container_name: keepnote-debug # Cold `docker compose -f docker-compose.debug.yaml up -d` builds this image # directly — no pre-build step needed. Do NOT add `network: host` here: # `docker compose up`/`build` cannot grant the `network.host` build # entitlement (that needs `--allow=network.host`, which only `docker build` # accepts), so a cold up would fail with "additional privileges requested". # The default bridge reaches the dev Gitea (192.168.122.183), so # Dockerfile.debug's GOPROXY resolves shared modules without host networking. build: context: . dockerfile: Dockerfile.debug # `keepnote-debug` is a local-only tag: build it, never look it up in a # registry (otherwise a cold `up` warns "pull access denied"). pull_policy: build # Expose ports: host:container ports: - "8081:8080" # The SINGLE port: API + SPA + /web/* (contract rev 9) # Delve: unique host port per service (auth-debug owns 2345), so the Go # attach launch config reaches Air's Delve listener (in-container 2345) # from the host without conflicts. - "2346:2345" # Delve debugger port # Volume mounts - THE KEY TO HOT RELOADING # Mount source code as a volume so changes are reflected immediately volumes: - ./src:/app - go-mod-cache:/go/pkg/mod - go-build-cache:/root/.cache/go-build - /app/src/tmp # Exclude Air's tmp dir from syncing # Browser-edge SPA overlay (contract §10.3): host `bun run build` output is # served without a Go rebuild; falls back to the embedded placeholder when # absent. DCNTRLIZED_WEB_DIR is honoured only in dev/debug mode. - ./web/dist:/web-dist:ro working_dir: /app/ # Environment variables for the application environment: # DCNTRLIZED_DEV is the single dev selector (registry/auth URLs). - DCNTRLIZED_DEV=true # Contract rev 9: internal traffic is plaintext, so the debug selector no # longer gates any listener — this listener is ALWAYS plaintext now. - DCNTRLIZED_DEBUG=true # Browser edge (contract rev 9 §17.2): the SPA and /web/* ride the single # :8080 port, served from the host-built overlay; dev is HTTP so # COOKIE_SECURE=false. - DCNTRLIZED_WEB_ENABLED=true - DCNTRLIZED_WEB_DIR=/web-dist - COOKIE_SECURE=false # Hub base URL for the plaintext internal hop (rev 9: no mTLS identity). # Overridable so a throwaway hub can be exercised without touching the # shared dev stack. - AUTH_SERVICE_URL=${AUTH_SERVICE_URL:-http://dcntrlized-auth:8080} # Application configuration # - PORT=8080 # - ENVIRONMENT=development # - LOG_LEVEL=debug # Database connection (shared database on the dcntrlized network) - DATABASE_URL=postgres://postgres:dcntrlized@dcntrlized-db:5432/dcntrlized?sslmode=disable # Go specific settings - GO111MODULE=on - GOPROXY=http://192.168.122.183:3000/api/packages/Dcntrlized/go,https://proxy.golang.org,direct - GONOSUMDB=git.dcntrlized.org depends_on: - migrate # Restart policy for development restart: unless-stopped # Network for service communication networks: - dcntrlized_net cap_add: - SYS_PTRACE security_opt: - apparmor:unconfined migrate: image: postgres:16-alpine container_name: keepnote-migrate labels: org.dcntrlized.oneoff: "true" networks: - dcntrlized_net volumes: - ./database/migrations:/migrations:ro environment: - PGHOST=dcntrlized-db - PGPORT=5432 - PGUSER=postgres - PGPASSWORD=dcntrlized - PGDATABASE=dcntrlized command: > sh -c 'until pg_isready -h dcntrlized-db -U postgres -d dcntrlized; do sleep 2; done; for f in /migrations/*.sql; do [ -f "$$f" ] || continue; echo "Applying $$f"; psql -v ON_ERROR_STOP=1 -d dcntrlized -f "$$f" || exit 1; done' # Named volumes for data persistence and caching volumes: # Go module cache - shared across rebuilds go-mod-cache: driver: local # Go build cache - speeds up compilation go-build-cache: driver: local networks: dcntrlized_net: external: true