4.6 KiB
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:
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:
docker-compose.yml— defines services (app, postgres, redis)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.
# 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.
# 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
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 installhappens INSIDE the container (not on host)COPY . .copies your source into the containerCMDmust start your app- For TypeScript: add
RUN npm run buildbefore CMD, useCMD ["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 withsaac 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:
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 onsaac deploy - Hot-reload (
yourapp-hot.startanaicompany.com) — volume-mounted source, auto-updates ongit push
Debugging Commands
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.