# ============================================================================ # Krow API — Docker environment # # cd infrastructure && cp .env.docker.example .env # # Compose reads `.env` from the directory the compose file lives in, so this # copy belongs in infrastructure/, NOT the repository root .env used by # `make run`. Both are gitignored. # # Every value here is a placeholder. No real password or host belongs in a # file that is committed. # ============================================================================ # ── Application ───────────────────────────────────────────────────────────── APP_ENV=production # development | staging | production LOG_LEVEL=info # debug | info | warn | error VERSION=1.0.0 # build arg only IMAGE=doormile/krowbackend:latest # what `docker compose build` tags and `up` runs # ── Where the API is published ────────────────────────────────────────────── # Loopback by default: put a reverse proxy in front to terminate TLS. The API # speaks plain HTTP and its session cookie is Secure outside development, so a # browser will not send that cookie back over a plain connection anyway. API_BIND=127.0.0.1 API_PORT=8080 # Browser origins allowed to call this API, comma-separated. Exact strings: # scheme included, no trailing slash. Cookies are the auth mechanism, so # anything listed here can hold a live session. # # "*" is REJECTED at startup — browsers refuse Allow-Origin: "*" together with # credentials, so it would break every authenticated call rather than loosen # anything. HTTP_CORS_ORIGINS=https://platform.krowforce.com,https://mcp.krowforce.com # SameSite on the session cookie: lax | none | strict. # # "lax" is right while the API is on *.krowforce.com — the two origins above # are then cross-ORIGIN (CORS applies) but same-SITE (the cookie is still # sent). Move the API to any other registrable domain and this must become # "none", or login will succeed and every following request will arrive # anonymous. HTTP_COOKIE_SAMESITE=lax # ── Database ──────────────────────────────────────────────────────────────── # Point at a managed PostgreSQL. With docker-compose.local-db.yml layered on # top, set DATABASE_HOST=postgres instead. DATABASE_HOST=your-database-host.example.com DATABASE_PORT=5432 DATABASE_NAME=krow DATABASE_USER=krow DATABASE_PASSWORD=change-me DATABASE_SCHEMA=public # require encrypts but does not verify the server; verify-full also checks the # certificate against a CA and is what you want against a managed database. # # `disable` is REJECTED at startup when APP_ENV=production. That is deliberate. DATABASE_SSLMODE=require # ── Migration URL ─────────────────────────────────────────────────────────── # golang-migrate takes a single URL, and Compose cannot assemble or encode one. # # ⚠️ PERCENT-ENCODE THE PASSWORD. A password containing @ : / ? # or % will # otherwise be parsed as part of the host or the path, and the failure looks # like a wrong host rather than a wrong password: # # @ → %40 : → %3A / → %2F ? → %3F # → %23 % → %25 # # python3 -c 'import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1], safe=""))' 'your-password' # # search_path must match DATABASE_SCHEMA above. DATABASE_URL=postgres://krow:change-me@your-database-host.example.com:5432/krow?sslmode=require&search_path=public # ── Pool and timeouts ─────────────────────────────────────────────────────── DATABASE_MAX_OPEN_CONNS=25 DATABASE_MIN_IDLE_CONNS=2 DATABASE_CONN_MAX_LIFETIME=30m DATABASE_CONNECT_TIMEOUT=10s DATABASE_STATEMENT_TIMEOUT=10s # ── HTTP timeouts ─────────────────────────────────────────────────────────── HTTP_READ_TIMEOUT=15s HTTP_WRITE_TIMEOUT=30s HTTP_IDLE_TIMEOUT=60s HTTP_SHUTDOWN_TIMEOUT=10s # ── Container resources ───────────────────────────────────────────────────── API_CPU_LIMIT=2 API_MEMORY_LIMIT=512M # ── Local database only (docker-compose.local-db.yml) ─────────────────────── POSTGRES_BIND=127.0.0.1 POSTGRES_PORT=5432 POSTGRES_MEMORY_LIMIT=1G