# 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. ## Your Two Domains Every app gets **two live URLs** on the server. Both are created automatically when you deploy: ``` yourapp..startanaicompany.com ← PRODUCTION (customers see this) yourapp-hot..startanaicompany.com ← HOT-RELOAD (you develop with this) ``` For example, if your app domain is `my-saas.adam.startanaicompany.com`: - Production: `https://my-saas.adam.startanaicompany.com` - Hot-reload: `https://my-saas-hot.adam.startanaicompany.com` ### How to Use Them **Hot-reload (`-hot` domain)** — Use this while developing. Every `git push` automatically: 1. Pulls your latest code 2. Rebuilds the React frontend (`npm run build`) 3. Restarts the Express server 4. Your changes are live in ~10-20 seconds **Recommended development workflow:** ```bash # 1. Make your code changes # 2. Push to git git add . && git commit -m "Add login page" && git push # 3. Wait ~15 seconds, then check the hot domain curl https://yourapp-hot.adam.startanaicompany.com/health # 4. Once it works on hot, deploy to production saac deploy # 5. Verify production curl https://yourapp.adam.startanaicompany.com/health ``` **Production domain** — Only updates when you run `saac deploy`. This rebuilds the Docker image from your Dockerfile and restarts everything. Use this for the final, tested version. ### Why Two Containers? | Container | Domain | How it runs your code | When it updates | |-----------|--------|----------------------|-----------------| | **Production** | `yourapp.example.com` | Built from your `Dockerfile` (immutable Docker image) | Only on `saac deploy` | | **Hot-reload** | `yourapp-hot.example.com` | Volume-mounts your git repo, runs `npm run dev` via nodemon | Automatically on every `git push` | **Key differences:** - **Production** uses your Dockerfile. The Docker image is built once and runs until next deploy. - **Hot-reload** does NOT use your Dockerfile. It mounts your source code directly, installs dependencies, builds the React client, and runs Express with nodemon. - **Hot-reload auto-rebuilds everything** — `nodemon.json` watches both `server.js` and `client/src/`. When files change (after `git push`), it runs `npm run build` (React) and restarts `node server.js`. ### How nodemon.json Works The `nodemon.json` file in your repo tells the hot-reload container what to watch: ```json { "watch": ["server.js", "client/src"], "ext": "js,ts,tsx,jsx,css,json", "exec": "npm run build && node server.js" } ``` - **watch**: Directories/files to monitor for changes - **ext**: File extensions that trigger a rebuild - **exec**: What to run when changes are detected — rebuilds React, restarts Express **You can customize this.** For example, if you add a `lib/` directory with shared code: ```json { "watch": ["server.js", "client/src", "lib"], "ext": "js,ts,tsx,jsx,css,json", "exec": "npm run build && node server.js" } ``` ## 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 ``` ### 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/postgres postgres: environment: - POSTGRES_DB=postgres # 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, push, check the hot domain - When it works, add the next feature - **Coordinate deploys** — only ONE agent should push/deploy at a time ### Rule 5: 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"] ``` 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"] ``` ## Environment Variables 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 (production container) saac logs --type build # Build/deploy logs 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 ``` ## Quick Reference: Development Cycle ``` git push ──→ hot-reload updates (~15s) ──→ check yourapp-hot domain │ │ │ ┌─────── works? ──┘ │ │ │ YES ────┤──── NO │ │ │ │ │ saac deploy │ fix code, git push again │ │ │ │ production │ │ updated │ └────────────────────────────┘ ``` ## Common Mistakes and Fixes | Mistake | Fix | |---------|-----| | `ports: "3000:3000"` | Change to `expose: ["3000"]` | | `DB_HOST=localhost` | Change to `DB_HOST=postgres` (service name) | | 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 | | Multiple agents deploying simultaneously | Coordinate — one agent deploys at a time | | "App serves old code" on production | Run `saac deploy` to rebuild the Docker image | | "App serves old code" on hot domain | Wait ~15s after push. Check `saac logs` for rebuild errors | | Hot-reload container crashes | Check `saac logs` — usually a missing dependency or build error | ## 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.