DevOps & Infrastructure
The complete AIBMM production stack: DigitalOcean App Platform for hosting, Managed PostgreSQL for the database, GitHub Container Registry for Docker images, GitHub Actions for CI/CD, and how it all connects to your Cursor-based development workflow.
Architecture Overview
The AIBMM stack has two deployment targets — a dev server for testing and DigitalOcean App Platform for production — connected through a GitHub-based CI/CD pipeline.
Full Pipeline
1. Local Dev
Cursor IDE (SSH)
Feature branch
BobTheBuilder.sh
2. Dev Server
docker compose up
dev.aibmm.ai
Live testing
3. GitHub
Push to main
GitHub Actions
GHCR image push
4. DigitalOcean
App Platform
Managed PostgreSQL
aibmm.ai
Flow: Dev deploy
Cursor → BobTheBuilder (option 1) → docker compose build → dev.aibmm.ai
Flow: Production deploy
Cursor → BobTheBuilder (option 3) → merge to main → GitHub Actions → GHCR → DO App Platform → aibmm.ai
| Service | Role | URL |
|---|---|---|
| DigitalOcean App Platform | Production app hosting (pulls Docker image from GHCR) | cloud.digitalocean.com/apps |
| DigitalOcean Managed PostgreSQL | Production database with SSL | cloud.digitalocean.com/databases |
| GitHub Container Registry (GHCR) | Docker image storage and versioning | ghcr.io |
| GitHub Actions | CI/CD: test → build → push → deploy | github.com → Actions tab |
| Docker / docker-compose | Dev environment containerization | docs.docker.com |
| doctl CLI | DigitalOcean CLI used by GitHub Actions to trigger deploys | docs.digitalocean.com/reference/doctl |
DigitalOcean Setup
Create your DigitalOcean account, provision a managed PostgreSQL database, and set up App Platform to host your Docker image.
Create a DigitalOcean Account
Sign up at cloud.digitalocean.com. DigitalOcean offers a $200 free credit for new accounts, which covers several months of the AIBMM stack.
Provision a Managed PostgreSQL Database
Go to Databases → Create Database → PostgreSQL. Choose the same region as your App Platform instance (e.g., NYC3).
Once provisioned, copy the connection string from the Connection Details tab. It will look like:
DATABASE_URL format
postgresql://doadmin:YOUR_PASSWORD@db-postgresql-nyc3-XXXXX.db.ondigitalocean.com:25060/defaultdb?sslmode=requireAdd this to your App Platform environment variables as DATABASE_URL.
Set Up App Platform
Go to Apps → Create App → Container Registry → GitHub Container Registry (GHCR). Enter your image path: ghcr.io/YOUR_ORG/YOUR_REPO:latest
Configure the following in App Platform settings:
| Setting | Value |
|---|---|
| HTTP Port | 9000 |
| Health Check Path | /api/health |
| Health Check Initial Delay | 30 seconds |
| Run Command | (leave empty — uses Dockerfile CMD) |
Set all required environment variables in the App Platform UI (not in the repo):
Required App Platform Environment Variables
DATABASE_URL=postgresql://doadmin:...@db-host:25060/defaultdb?sslmode=require
NEXTAUTH_SECRET=your-random-secret-min-32-chars
NEXTAUTH_URL=https://yourdomain.com
AUTH_TRUST_HOST=true
ADMIN_USERNAME=admin
ADMIN_PASSWORD=your-secure-password
ADMIN_EMAIL=admin@yourdomain.com
SETTINGS_ENCRYPTION_KEY=your-32-byte-hex-key
PORT=9000
HOSTNAME=0.0.0.0
NODE_TLS_REJECT_UNAUTHORIZED=0Install doctl CLI
doctl is the DigitalOcean CLI. It is used by GitHub Actions to trigger deployments. You do not need it locally unless you are debugging deployments.
Install doctl (Ubuntu/Debian)
# Download and install
cd ~
wget https://github.com/digitalocean/doctl/releases/download/v1.104.0/doctl-1.104.0-linux-amd64.tar.gz
tar xf doctl-1.104.0-linux-amd64.tar.gz
sudo mv doctl /usr/local/bin
# Authenticate
doctl auth init
# Paste your DigitalOcean API token when prompted
# Verify
doctl account getYour DigitalOcean API token is created at cloud.digitalocean.com/account/api/tokens. Generate a token with Write scope and save it — you will also need it as a GitHub secret.
GitHub Container Registry (GHCR)
Enable GitHub Container Registry (GHCR) on your repository and understand how Docker images are built, tagged, and stored.
Enable GHCR on Your Repository
GHCR is enabled automatically for any GitHub repository. No setup required — your GitHub Actions workflow will push images to ghcr.io/YOUR_ORG/YOUR_REPO using the built-in GITHUB_TOKEN.
To make the image publicly accessible (required for DigitalOcean App Platform to pull without credentials), go to your GitHub repository → Packages → your image → Package settings → set visibility to Public.
Understand the Docker Image Build
The Dockerfile uses a three-stage build to produce a minimal production image:
Dockerfile stages (simplified)
# Stage 1: Install dependencies
FROM node:20-alpine AS deps
COPY package*.json ./
RUN npm ci
# Stage 2: Build the Next.js app
FROM node:20-alpine AS builder
ARG GIT_COMMIT
ARG GIT_BRANCH
ARG BUILD_TIME
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
# Stage 3: Minimal runtime image
FROM node:20-alpine AS runner
# Copy only the standalone output + static files
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
# Copy migration runner and entrypoint
COPY scripts/run-migrations.js ./scripts/
COPY scripts/docker-entrypoint.sh ./
EXPOSE 9000
CMD ["sh", "docker-entrypoint.sh"]Image Tagging Strategy
GitHub Actions builds and pushes the image with multiple tags on every push to main:
Image tags produced by GitHub Actions
ghcr.io/your-org/your-repo:latest # Always points to latest main
ghcr.io/your-org/your-repo:main # Branch name
ghcr.io/your-org/your-repo:sha-a1b2c3d # Short commit SHA
ghcr.io/your-org/your-repo:v1.2.3 # Semver tag (when you push a tag)DigitalOcean App Platform is configured to use the :latest tag. When a new :latest is pushed, App Platform pulls and deploys it automatically when triggered by doctl.
GitHub Actions CI/CD
Understand the three-job CI/CD pipeline: test, build & push to GHCR, and deploy to DigitalOcean App Platform.
Add Required GitHub Secrets
Go to your GitHub repository → Settings → Secrets and variables → Actions → New repository secret.
| Secret Name | Value | Required? |
|---|---|---|
| DIGITALOCEAN_ACCESS_TOKEN | Your DO API token (Write scope) | Yes |
| GITHUB_TOKEN | Auto-provided by GitHub Actions | Auto |
Understand the Three-Job Pipeline
The workflow in .github/workflows/docker-publish.yml runs three jobs in sequence on every push to main:
Job 1: test
test job
- npm ci
- npm run lint # ESLint
- npm run type-check # TypeScript
- npm run test:smoke # Playwright smoke tests against dev.aibmm.aiJob 2: build-and-push (depends on test)
build-and-push job
- Set up Docker Buildx
- Log in to GHCR using GITHUB_TOKEN
- Build Docker image with build args:
GIT_COMMIT: ${{ github.sha }}
GIT_BRANCH: ${{ github.ref_name }}
BUILD_TIME: ${{ now }}
- Push image with all tags (latest, branch, sha, semver)
- Cache layers in GitHub Actions cacheJob 3: deploy (depends on build-and-push, main branch only)
deploy job
- Install doctl
- Authenticate with DIGITALOCEAN_ACCESS_TOKEN
- Run: doctl apps create-deployment YOUR_APP_ID --force-rebuild --wait
# This tells DO App Platform to pull the new :latest image and deploy itFind Your App ID
The doctl deploy command requires your DigitalOcean App ID. Find it in the App Platform URL or via CLI:
Get your App ID
doctl apps list
# Output:
# ID Spec Name ...
# 44d9d422-6ab7-4ea4-abf7-63b9844d1236 aibmm-ia ...Paste this ID into your workflow file at the doctl apps create-deployment step.
Docker Configuration
How Docker is used for both local development (docker-compose) and production (standalone image), including automatic database migrations on startup.
Local Development with docker-compose
The docker-compose.yml builds and runs the app locally. It reads environment variables from your .env file and passes them into the container.
Deploy to dev server
# Build and start (used by BobTheBuilder option 0 and 1)
docker compose up -d --build
# View logs
docker compose logs -f
# Stop
docker compose downThe dev server runs on port 9000 and is proxied to dev.aibmm.ai via nginx (or your reverse proxy of choice).
Automatic Database Migrations on Startup
The container entrypoint runs database migrations before starting the app. This means every deploy automatically applies any pending schema changes — no manual migration step needed.
scripts/docker-entrypoint.sh
#!/bin/sh
set -e
# Run migrations if DATABASE_URL is set
if [ -n "$DATABASE_URL" ]; then
echo "Running database migrations..."
node /app/scripts/run-migrations.js
echo "Migrations complete."
fi
# Start the Next.js server
exec node server.jsHealth Check Endpoint
The app exposes a /api/health endpoint that returns build metadata. This is used by both Docker and DigitalOcean App Platform to verify the container is healthy, and by BobTheBuilder to confirm a successful production deployment.
GET /api/health response
{
"status": "ok",
"build": {
"commit": "a1b2c3d",
"branch": "main",
"time": "2026-03-17T12:00:00.000Z"
}
}BobTheBuilder polls this endpoint after a production deploy and compares the build.commit value against the commit that was just merged to main. If they match within 15 minutes, the deployment is verified as successful.
DNS and Domain Setup
Point your domain to DigitalOcean App Platform for production and configure a subdomain for your dev server.
Production Domain (App Platform)
In DigitalOcean App Platform → your app → Settings → Domains → Add Domain. Enter your domain (e.g., yourdomain.com).
DigitalOcean will provide a CNAME target. Add this to your DNS registrar:
DNS records for production
# At your domain registrar or DNS provider:
CNAME @ your-app.ondigitalocean.app. # Root domain
CNAME www your-app.ondigitalocean.app. # www subdomainDev Subdomain (Dev Server)
The dev server runs on your Linux VM (the same server you set up in Getting Started). Add an A record pointing to your VM's IP address:
DNS record for dev server
# At your domain registrar or DNS provider:
A dev YOUR_VM_IP_ADDRESS # e.g., dev.yourdomain.com → 123.45.67.89Then configure nginx on your VM to proxy port 80/443 to the Docker container on port 9000:
nginx config for dev subdomain
server {
listen 80;
server_name dev.yourdomain.com;
location / {
proxy_pass http://localhost:9000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# Use certbot to add SSL: sudo certbot --nginx -d dev.yourdomain.comUpdate NEXTAUTH_URL
NextAuth requires NEXTAUTH_URL to match the actual URL of your app. Set this in both environments:
Environment-specific NEXTAUTH_URL
# Dev server (.env file on the VM)
NEXTAUTH_URL=https://dev.yourdomain.com
# Production (App Platform environment variables)
NEXTAUTH_URL=https://yourdomain.comDatabase Migrations
How database schema changes are managed with TypeORM migrations — from development to production.
Development Workflow
During development, use the TypeORM CLI to manage schema changes:
TypeORM migration commands
# After modifying an entity in src/lib/db/entities/
npm run migration:generate -- -n AddPhoneNumber
# Generates: src/lib/db/migrations/1234567890-AddPhoneNumber.ts
# Review the generated migration, then apply it
npm run migration:run
# Revert the last migration if needed
npm run migration:revert
# Show migration status
npm run migration:showProduction Migration Deployment
Migrations run automatically on every container startup via docker-entrypoint.sh. When you deploy a new version, the container starts, runs any pending migrations, then starts the Next.js server.
The production migration runner (scripts/run-migrations.js) is a plain JavaScript file that does not require TypeScript compilation. It tracks applied migrations in a _migrations_js table.
Connecting It All to Cursor
How BobTheBuilder ties the entire workflow together — from your Cursor IDE to a verified production deployment.
The Full Developer Workflow
Once your infrastructure is set up, the day-to-day development workflow is:
Daily development workflow
# 1. Open Cursor IDE connected to your dev server via SSH
# (see Getting Started guide for SSH setup)
# 2. Create a feature branch
git checkout -b branch-2026-03-17-my-feature
# 3. Make code changes in Cursor
# 4. Quick type check (catches errors before deploy)
npm run quick-check
# 5. Deploy to dev for testing
./scripts/BobTheBuilder.sh 1 --yes
# → Runs TypeScript + lint + format checks
# → Builds Docker image
# → Deploys to dev.yourdomain.com
# → Test your changes at https://dev.yourdomain.com
# 6. When satisfied, deploy to production
./scripts/BobTheBuilder.sh 3 --yes
# → Runs all checks
# → Commits any auto-fixed files
# → Merges feature branch → main
# → Pushes to GitHub
# → GitHub Actions: test → build → push to GHCR → deploy to DO
# → Polls /api/health until new commit is live (up to 15 min)
# → Confirms deployment successfulBobTheBuilder Option Reference
| Option | Command | What It Does |
|---|---|---|
| 0 | ./scripts/BobTheBuilder.sh 0 --yes | Quick deploy to dev (skip checks, use Docker cache) |
| 1 | ./scripts/BobTheBuilder.sh 1 --yes | Deploy to dev with TypeScript, lint, and format checks |
| 3 | ./scripts/BobTheBuilder.sh 3 --yes | Full production deploy: checks → merge to main → push → CI/CD → verify |
| 4 | ./scripts/BobTheBuilder.sh 4 --yes | Run API tests against dev |
| 5 | ./scripts/BobTheBuilder.sh 5 --yes | Run Playwright E2E tests against dev |
Verifying a Production Deployment
BobTheBuilder automatically verifies the deployment by polling the health endpoint. You can also check manually:
Manual deployment verification
# Check what commit is live in production
curl -s https://yourdomain.com/api/health | jq '.build.commit'
# Compare with the latest commit on main
git log main --oneline -1
# Check GitHub Actions status
# https://github.com/YOUR_ORG/YOUR_REPO/actionsQuick Reference
Service URLs
DigitalOcean Console
https://cloud.digitalocean.com
App Platform
https://cloud.digitalocean.com/apps
Managed Databases
https://cloud.digitalocean.com/databases
API Tokens
https://cloud.digitalocean.com/account/api/tokens
GitHub Packages (GHCR)
https://github.com/YOUR_ORG?tab=packages
GitHub Actions
https://github.com/YOUR_ORG/YOUR_REPO/actions
Key Commands
Quick type check
npm run quick-check
Deploy to dev
./scripts/BobTheBuilder.sh 1 --yes
Deploy to production
./scripts/BobTheBuilder.sh 3 --yes
Run migrations (dev)
npm run migration:run
Generate migration
npm run migration:generate -- -n Name
Check production health
curl https://yourdomain.com/api/health
Next Steps
With your infrastructure running, explore these resources to complete your development workflow: