Update SAAC_DEPLOYMENT.md: add dual-container docs, DB name warning, deploy coordination
This commit is contained in:
@@ -20,6 +20,23 @@ Your repo MUST have:
|
|||||||
1. `docker-compose.yml` — defines services (app, postgres, redis)
|
1. `docker-compose.yml` — defines services (app, postgres, redis)
|
||||||
2. `Dockerfile` — referenced by the build directive in compose
|
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
|
## The 5 Rules
|
||||||
|
|
||||||
### Rule 1: Use `expose`, NEVER `ports`
|
### 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
|
# CORRECT — "postgres" is the service name in docker-compose.yml
|
||||||
DATABASE_URL: postgresql://postgres:postgres@postgres:5432/mydb
|
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
|
```dockerfile
|
||||||
FROM node:20-alpine
|
FROM node:20-alpine
|
||||||
@@ -63,64 +109,30 @@ COPY . .
|
|||||||
CMD ["node", "server.js"]
|
CMD ["node", "server.js"]
|
||||||
```
|
```
|
||||||
|
|
||||||
Key points:
|
For TypeScript projects:
|
||||||
- `npm install` happens INSIDE the container (not on host)
|
```dockerfile
|
||||||
- `COPY . .` copies your source into the container
|
FROM node:20-alpine
|
||||||
- `CMD` must start your app
|
WORKDIR /app
|
||||||
- For TypeScript: add `RUN npm run build` before CMD, use `CMD ["node", "dist/server.js"]`
|
COPY package*.json ./
|
||||||
|
RUN npm install # Install ALL deps (including devDependencies for tsc)
|
||||||
### Rule 4: Keep it simple — iterate incrementally
|
COPY . .
|
||||||
|
RUN npm run build # Compile TypeScript
|
||||||
**Start with the working template, then add features one at a time.**
|
RUN npm prune --production # Remove devDeps from final image
|
||||||
|
CMD ["node", "dist/server.js"]
|
||||||
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
|
## 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:
|
Env vars set via `saac env set KEY=VALUE` are written to `.env` in your repo directory before build.
|
||||||
|
|
||||||
```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
|
## Debugging Commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
saac logs # Runtime logs
|
saac logs # Runtime logs (production container)
|
||||||
saac logs --type build # Build/deploy logs
|
saac logs --type build # Build/deploy logs
|
||||||
saac exec "ls -la" # Run command in container
|
saac exec "ls -la" # Run command in production container
|
||||||
saac exec "cat package.json" # Check what's actually in the container
|
saac exec "cat package.json" # Check what's in the container
|
||||||
saac db sql "SELECT * FROM users LIMIT 5" # Query database
|
saac db sql "SELECT * FROM users LIMIT 5" # Query database
|
||||||
saac db containers # List database containers
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Common Mistakes and Fixes
|
## Common Mistakes and Fixes
|
||||||
@@ -129,12 +141,12 @@ saac db containers # List database containers
|
|||||||
|---------|-----|
|
|---------|-----|
|
||||||
| `ports: "3000:3000"` | Change to `expose: ["3000"]` |
|
| `ports: "3000:3000"` | Change to `expose: ["3000"]` |
|
||||||
| `DB_HOST=localhost` | Change to `DB_HOST=postgres` (service name) |
|
| `DB_HOST=localhost` | Change to `DB_HOST=postgres` (service name) |
|
||||||
| No Dockerfile | Create one (see Rule 3) |
|
| Changed `POSTGRES_DB` name after first deploy | Keep original name or delete postgres volume |
|
||||||
| TypeScript not compiling | Add `RUN npm run build` in Dockerfile |
|
| `tsc: not found` in Dockerfile | Install ALL deps first: `RUN npm install` (not `--production`) |
|
||||||
| `.dockerignore` has `dist/` | Remove it — dist is built inside container |
|
| `.dockerignore` has `dist/` | Remove it — dist is built inside container |
|
||||||
| `npm start` undefined | Add `"start": "node server.js"` to package.json scripts |
|
| Multiple agents deploying simultaneously | Coordinate — one agent deploys at a time |
|
||||||
| Container exits immediately | Check `saac logs` — usually a missing env var or import error |
|
| "App serves old code" | Check production domain (not hot-reload). Run `saac deploy` to rebuild. |
|
||||||
| "ECONNREFUSED 127.0.0.1" | You're using localhost — change to the Docker service name |
|
| Hot-reload container crashes | This is separate from production. Fix `package.json` `dev` script. |
|
||||||
|
|
||||||
## Do NOT Add Traefik Labels
|
## Do NOT Add Traefik Labels
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user