AI Business Maturity Model
Certifications
Find a CoachFind a SpeakerSign In

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.

DevOps
DigitalOcean
CI/CD
Docker
PostgreSQL
GitHub Actions

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

ServiceRoleURL
DigitalOcean App PlatformProduction app hosting (pulls Docker image from GHCR)cloud.digitalocean.com/apps
DigitalOcean Managed PostgreSQLProduction database with SSLcloud.digitalocean.com/databases
GitHub Container Registry (GHCR)Docker image storage and versioningghcr.io
GitHub ActionsCI/CD: test → build → push → deploygithub.com → Actions tab
Docker / docker-composeDev environment containerizationdocs.docker.com
doctl CLIDigitalOcean CLI used by GitHub Actions to trigger deploysdocs.digitalocean.com/reference/doctl

Phase 1

DigitalOcean Setup

Create your DigitalOcean account, provision a managed PostgreSQL database, and set up App Platform to host your Docker image.

1
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.

2
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

env
postgresql://doadmin:[email protected]:25060/defaultdb?sslmode=require

Add this to your App Platform environment variables as DATABASE_URL.

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

SettingValue
HTTP Port9000
Health Check Path/api/health
Health Check Initial Delay30 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

env
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
[email protected]
SETTINGS_ENCRYPTION_KEY=your-32-byte-hex-key
PORT=9000
HOSTNAME=0.0.0.0
NODE_TLS_REJECT_UNAUTHORIZED=0
4
Install doctl CLI
Optional

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)

bash
# 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 get

Your 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.


Phase 2

GitHub Container Registry (GHCR)

Enable GitHub Container Registry (GHCR) on your repository and understand how Docker images are built, tagged, and stored.

1
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.

2
Understand the Docker Image Build

The Dockerfile uses a three-stage build to produce a minimal production image:

Dockerfile stages (simplified)

dockerfile
# 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"]
3
Image Tagging Strategy

GitHub Actions builds and pushes the image with multiple tags on every push to main:

Image tags produced by GitHub Actions

yaml
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.


Phase 3

GitHub Actions CI/CD

Understand the three-job CI/CD pipeline: test, build & push to GHCR, and deploy to DigitalOcean App Platform.

1
Add Required GitHub Secrets

Go to your GitHub repository → Settings Secrets and variablesActions → New repository secret.

Secret NameValueRequired?
DIGITALOCEAN_ACCESS_TOKENYour DO API token (Write scope)Yes
GITHUB_TOKENAuto-provided by GitHub ActionsAuto
2
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

yaml
- npm ci
- npm run lint          # ESLint
- npm run type-check    # TypeScript
- npm run test:smoke    # Playwright smoke tests against dev.aibmm.ai

Job 2: build-and-push (depends on test)

build-and-push job

yaml
- 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 cache

Job 3: deploy (depends on build-and-push, main branch only)

deploy job

yaml
- 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 it
3
Find 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

bash
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.


Phase 4

Docker Configuration

How Docker is used for both local development (docker-compose) and production (standalone image), including automatic database migrations on startup.

1
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

bash
# Build and start (used by BobTheBuilder option 0 and 1)
docker compose up -d --build

# View logs
docker compose logs -f

# Stop
docker compose down

The dev server runs on port 9000 and is proxied to dev.aibmm.ai via nginx (or your reverse proxy of choice).

2
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

bash
#!/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.js
3
Health 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

json
{
  "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.


Phase 5

DNS and Domain Setup

Point your domain to DigitalOcean App Platform for production and configure a subdomain for your dev server.

1
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

dns
# At your domain registrar or DNS provider:
CNAME  @    your-app.ondigitalocean.app.   # Root domain
CNAME  www  your-app.ondigitalocean.app.   # www subdomain
2
Dev 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

dns
# At your domain registrar or DNS provider:
A  dev  YOUR_VM_IP_ADDRESS   # e.g., dev.yourdomain.com → 123.45.67.89

Then configure nginx on your VM to proxy port 80/443 to the Docker container on port 9000:

nginx config for dev subdomain

nginx
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.com
3
Update NEXTAUTH_URL

NextAuth requires NEXTAUTH_URL to match the actual URL of your app. Set this in both environments:

Environment-specific NEXTAUTH_URL

env
# Dev server (.env file on the VM)
NEXTAUTH_URL=https://dev.yourdomain.com

# Production (App Platform environment variables)
NEXTAUTH_URL=https://yourdomain.com

Phase 6

Database Migrations

How database schema changes are managed with TypeORM migrations — from development to production.

1
Development Workflow

During development, use the TypeORM CLI to manage schema changes:

TypeORM migration commands

bash
# 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:show
2
Production 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.


Phase 7

Connecting It All to Cursor

How BobTheBuilder ties the entire workflow together — from your Cursor IDE to a verified production deployment.

1
The Full Developer Workflow

Once your infrastructure is set up, the day-to-day development workflow is:

Daily development workflow

bash
# 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 successful
2
BobTheBuilder Option Reference
OptionCommandWhat It Does
0./scripts/BobTheBuilder.sh 0 --yesQuick deploy to dev (skip checks, use Docker cache)
1./scripts/BobTheBuilder.sh 1 --yesDeploy to dev with TypeScript, lint, and format checks
3./scripts/BobTheBuilder.sh 3 --yesFull production deploy: checks → merge to main → push → CI/CD → verify
4./scripts/BobTheBuilder.sh 4 --yesRun API tests against dev
5./scripts/BobTheBuilder.sh 5 --yesRun Playwright E2E tests against dev
3
Verifying a Production Deployment

BobTheBuilder automatically verifies the deployment by polling the health endpoint. You can also check manually:

Manual deployment verification

bash
# 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/actions

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

Getting Started

Set up your Ubuntu dev server, Docker, and Cursor IDE via SSH