Files
template_001/SAAC_DEPLOYMENT.md

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:

  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.

# 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 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:

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

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.