From 2010a8d4ece406f63cdead7200633a95f97729a8 Mon Sep 17 00:00:00 2001 From: ryanadmin Date: Mon, 16 Feb 2026 18:53:39 +0000 Subject: [PATCH] Update SAAC_DEPLOYMENT.md: add dual-container docs, DB name warning, deploy coordination --- SAAC_DEPLOYMENT.md | 126 +++++++++++++++++++++++++-------------------- 1 file changed, 69 insertions(+), 57 deletions(-) diff --git a/SAAC_DEPLOYMENT.md b/SAAC_DEPLOYMENT.md index 1b19061..760a4f5 100644 --- a/SAAC_DEPLOYMENT.md +++ b/SAAC_DEPLOYMENT.md @@ -20,6 +20,23 @@ Your repo MUST have: 1. `docker-compose.yml` — defines services (app, postgres, redis) 2. `Dockerfile` — referenced by the build directive in compose +## CRITICAL: Two Containers Per App + +Every app gets TWO containers. Understanding this prevents the #1 debugging confusion: + +| Container | Domain | How it runs your code | When it updates | +|-----------|--------|----------------------|-----------------| +| **Production** | `yourapp.startanaicompany.com` | Built from your `Dockerfile` (immutable image) | Only on `saac deploy` | +| **Hot-reload** | `yourapp-hot.startanaicompany.com` | Volume-mounts your git repo, runs `npm run dev` | Instantly on `git push` | + +**Key facts:** +- **Production** runs the Docker image you built. Your Dockerfile, CMD, and build steps all apply. +- **Hot-reload** does NOT use your Dockerfile. It mounts your source code directly and runs nodemon/npm run dev. +- **Your main domain routes to PRODUCTION, not hot-reload.** If production works but hot-reload doesn't, your app is fine. +- **TypeScript projects**: Production container compiles TS in the Dockerfile (`RUN npm run build`). Hot-reload needs a `dev` script in package.json that compiles and runs (e.g., `"dev": "tsc && node dist/server.js"` or `"dev": "tsx watch src/server.ts"`). + +**Common confusion:** "My Docker build works but the app serves old code!" — You're probably looking at the hot-reload container. Check the PRODUCTION domain (without `-hot`). + ## The 5 Rules ### Rule 1: Use `expose`, NEVER `ports` @@ -46,12 +63,41 @@ DATABASE_URL: postgresql://postgres:postgres@localhost:5432/mydb # CORRECT — "postgres" is the service name in docker-compose.yml DATABASE_URL: postgresql://postgres:postgres@postgres:5432/mydb - -# ALSO CORRECT — if using shared org database -DATABASE_URL: postgresql://postgres:postgres@postgres.internal:5432/mydb ``` -### Rule 3: Dockerfile must produce a running container +### Rule 3: Database name must match between app and postgres service + +The `POSTGRES_DB` in your postgres service creates the database. Your app must connect to the SAME name. + +```yaml +services: + app: + environment: + # Must match POSTGRES_DB below! + - DATABASE_URL=postgresql://postgres:postgres@postgres:5432/template_db + postgres: + environment: + - POSTGRES_DB=template_db # This creates the database +``` + +**WARNING:** If you change `POSTGRES_DB` after first deploy, the old name persists in the Docker volume. The new name won't exist. Either keep the original name or destroy and recreate the postgres volume. + +### Rule 4: Keep it simple — iterate incrementally + +**Start with the working template, then add features one at a time.** + +DO NOT: +- Replace Express with TypeScript + Prisma + monorepo in one commit +- Create complex multi-stage Dockerfiles before the basic app works +- Have multiple agents push changes simultaneously (causes deploy loops) + +DO: +- Get the template deploying first (`saac deploy`, verify with `saac logs`) +- Add one feature, deploy, verify +- Add the next feature, deploy, verify +- **Coordinate deploys** — only ONE agent should push/deploy at a time + +### Rule 5: Dockerfile must produce a running container ```dockerfile FROM node:20-alpine @@ -63,64 +109,30 @@ COPY . . CMD ["node", "server.js"] ``` -Key points: -- `npm install` happens INSIDE the container (not on host) -- `COPY . .` copies your source into the container -- `CMD` must start your app -- For TypeScript: add `RUN npm run build` before CMD, use `CMD ["node", "dist/server.js"]` - -### Rule 4: Keep it simple — iterate incrementally - -**Start with the working template, then add features one at a time.** - -DO NOT: -- Replace Express with a full TypeScript/Prisma/monorepo stack in one commit -- Create complex multi-stage Dockerfiles before the basic app works -- Add workspace configurations before single-service works -- Change the directory structure before deploying successfully once - -DO: -- Get the template deploying first (`saac deploy`, verify with `saac logs`) -- Add one feature, deploy, verify -- Add the next feature, deploy, verify - -### Rule 5: .dockerignore should NOT exclude build outputs - +For TypeScript projects: +```dockerfile +FROM node:20-alpine +WORKDIR /app +COPY package*.json ./ +RUN npm install # Install ALL deps (including devDependencies for tsc) +COPY . . +RUN npm run build # Compile TypeScript +RUN npm prune --production # Remove devDeps from final image +CMD ["node", "dist/server.js"] ``` -# Good .dockerignore -node_modules -.git -.env -``` - -Do NOT add `dist/` or `build/` — those are generated INSIDE the container by `RUN npm run build`. The .dockerignore only affects what gets COPIED into the build context. ## Environment Variables -Env vars set via `saac env set KEY=VALUE` are written to `.env` in your repo directory before build. Reference them in docker-compose.yml: - -```yaml -environment: - - DATABASE_URL=${DATABASE_URL} -``` - -Or use `env_file: .env` on your service. - -## Two Containers Per App - -Every app gets: -- **Production** (`yourapp.startanaicompany.com`) — immutable image, rebuilt on `saac deploy` -- **Hot-reload** (`yourapp-hot.startanaicompany.com`) — volume-mounted source, auto-updates on `git push` +Env vars set via `saac env set KEY=VALUE` are written to `.env` in your repo directory before build. ## Debugging Commands ```bash -saac logs # Runtime logs +saac logs # Runtime logs (production container) saac logs --type build # Build/deploy logs -saac exec "ls -la" # Run command in container -saac exec "cat package.json" # Check what's actually in the container +saac exec "ls -la" # Run command in production container +saac exec "cat package.json" # Check what's in the container saac db sql "SELECT * FROM users LIMIT 5" # Query database -saac db containers # List database containers ``` ## Common Mistakes and Fixes @@ -129,12 +141,12 @@ saac db containers # List database containers |---------|-----| | `ports: "3000:3000"` | Change to `expose: ["3000"]` | | `DB_HOST=localhost` | Change to `DB_HOST=postgres` (service name) | -| No Dockerfile | Create one (see Rule 3) | -| TypeScript not compiling | Add `RUN npm run build` in Dockerfile | +| Changed `POSTGRES_DB` name after first deploy | Keep original name or delete postgres volume | +| `tsc: not found` in Dockerfile | Install ALL deps first: `RUN npm install` (not `--production`) | | `.dockerignore` has `dist/` | Remove it — dist is built inside container | -| `npm start` undefined | Add `"start": "node server.js"` to package.json scripts | -| Container exits immediately | Check `saac logs` — usually a missing env var or import error | -| "ECONNREFUSED 127.0.0.1" | You're using localhost — change to the Docker service name | +| Multiple agents deploying simultaneously | Coordinate — one agent deploys at a time | +| "App serves old code" | Check production domain (not hot-reload). Run `saac deploy` to rebuild. | +| Hot-reload container crashes | This is separate from production. Fix `package.json` `dev` script. | ## Do NOT Add Traefik Labels