From 6933097afd048ad9ad77882debd11fe1f4832f86 Mon Sep 17 00:00:00 2001 From: ryanadmin Date: Mon, 16 Feb 2026 18:00:48 +0000 Subject: [PATCH] Add SAAC_DEPLOYMENT.md - deployment rules for AI agents --- SAAC_DEPLOYMENT.md | 141 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 SAAC_DEPLOYMENT.md diff --git a/SAAC_DEPLOYMENT.md b/SAAC_DEPLOYMENT.md new file mode 100644 index 0000000..1b19061 --- /dev/null +++ b/SAAC_DEPLOYMENT.md @@ -0,0 +1,141 @@ +# SAAC Deployment — How Your App Gets Built + +> **READ THIS FIRST.** This file explains exactly how SAAC deploys your app. +> Understanding this will save you hours of debugging. + +## The Build Process + +When you run `saac deploy`, the daemon executes these exact commands in your repo: + +```bash +docker compose -f docker-compose.yml -f docker-compose.saac.yml build +docker compose -p saac-{uuid} -f docker-compose.yml -f docker-compose.saac.yml up -d +``` + +**Your `docker-compose.yml` and `Dockerfile` ARE used.** The auto-generated `docker-compose.saac.yml` overlay ADDS labels, networks, and restart policy — it never replaces your config. + +## Required Files + +Your repo MUST have: +1. `docker-compose.yml` — defines services (app, postgres, redis) +2. `Dockerfile` — referenced by the build directive in compose + +## The 5 Rules + +### Rule 1: Use `expose`, NEVER `ports` + +Traefik reverse proxy handles all external routing. Host port bindings conflict with other apps. + +```yaml +# WRONG — will conflict with other apps on the server +ports: + - "3000:3000" + +# CORRECT — Traefik routes traffic to this port +expose: + - "3000" +``` + +### Rule 2: Database host = service name, NOT localhost + +In Docker Compose, services talk to each other by service name. + +```yaml +# WRONG — localhost means "inside this container" in Docker +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 + +```dockerfile +FROM node:20-alpine +WORKDIR /app +COPY package*.json ./ +RUN npm install +COPY . . +# If TypeScript: RUN npm run build +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 + +``` +# 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` + +## Debugging Commands + +```bash +saac logs # Runtime logs +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 db sql "SELECT * FROM users LIMIT 5" # Query database +saac db containers # List database containers +``` + +## Common Mistakes and Fixes + +| Mistake | Fix | +|---------|-----| +| `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 | +| `.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 | + +## Do NOT Add Traefik Labels + +The SAAC daemon handles Traefik routing automatically via file provider. Traefik Docker labels in your docker-compose.yml are **ignored**. You can remove them.