Deployment

Docker Compose (Local Dev)

Run the full Makeugcads stack locally with Docker Compose

This guide walks you through running the complete Makeugcads stack on your local machine using Docker Compose. No cloud accounts or paid licenses are required.

Local validation and production deployment use different paths. Local source builds usually run docker compose -f compose.yml -f compose.local.yml --profile legacy-postgres up --build and explicitly enable embedded PostgreSQL. Production compose.yml pulls prebuilt GHCR images and receives DATABASE_URL / LANGGRAPH_DATABASE_URL from Dokploy Databases. Do not commit override files that contain real secrets or machine-specific settings.

Local vs Production

ScenarioImage sourceRecommended commandNotes
Local development / validationBuild from local sourcedocker compose -f compose.yml -f compose.local.yml --profile legacy-postgres up --buildLocal override replaces web / agents images with build: and explicitly enables embedded PostgreSQL
Production DokployPrebuilt GHCR imagesDokploy RedeployPulls web, agents, migrations, and rbac-init, then connects to Dokploy Databases PostgreSQL

Production compose.yml uses these image variables:

GHCR_IMAGE_PREFIX=ghcr.io/chenhaonan-eth/ugcad
IMAGE_TAG=latest

The resulting images are:

${GHCR_IMAGE_PREFIX}-web:${IMAGE_TAG}
${GHCR_IMAGE_PREFIX}-agents:${IMAGE_TAG}
${GHCR_IMAGE_PREFIX}-migrations:${IMAGE_TAG}
${GHCR_IMAGE_PREFIX}-rbac-init:${IMAGE_TAG}

Prerequisites

  • Docker Desktop (Windows / macOS) or Docker Engine + Compose (Linux)
  • Git
  • An LLM API key (OpenAI or compatible)

Step 1 — Clone the Repository

git clone https://github.com/chenhaonan-eth/ugcad.git
cd ugcad

Step 2 — Create the .env File

Create a .env file and fill in your secrets. Do not commit .env, and do not put real secrets in documentation, Compose files, or override files:

# ── Image selection (used for production GHCR pulls; optional for local build override)
GHCR_IMAGE_PREFIX=ghcr.io/chenhaonan-eth/ugcad
IMAGE_TAG=latest

# ── Infrastructure ────────────────────────────────────
POSTGRES_PASSWORD=your_strong_password_here
DATABASE_URL=postgresql://ugcad:your_strong_password_here@postgres:5432/ugcad
LANGGRAPH_DATABASE_URL=postgresql://ugcad:your_strong_password_here@postgres:5432/langgraph
REDIS_PASSWORD=your_strong_redis_password

# ── Web ───────────────────────────────────────────────
AUTH_SECRET=your_32_char_secret_here_generate_with_openssl
NEXT_PUBLIC_APP_URL=http://localhost:3000

# ── Inter-service auth ────────────────────────────────
AGENTS_SHARED_SECRET=random_shared_secret

# ── LLM ───────────────────────────────────────────────
OPENAI_API_KEY=sk-your-key-here
# If using a third-party OpenAI-compatible API:
# OPENAI_BASE_URL=https://your-provider.com/v1
# OPENAI_MODEL=gpt-4o

Generate strong secrets with:

openssl rand -base64 32   # for AUTH_SECRET
openssl rand -hex 20      # for passwords

Step 3 — Add a Local Source Build Override

Production compose.yml pulls GHCR images by default. To build from the current source tree locally, create a local-only compose.local.yml:

services:
  agents:
    build:
      context: .
      dockerfile: docker/Dockerfile.agents-dev
    image: ugcad-agents:local
    ports:
      - "2024:8000"

  web:
    build:
      context: .
      dockerfile: apps/web/Dockerfile
      args:
        NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL:-http://localhost:3000}
        NEXT_PUBLIC_APP_NAME: ${NEXT_PUBLIC_APP_NAME:-Makeugcads}
        NEXT_PUBLIC_THEME: ${NEXT_PUBLIC_THEME:-huxibo}
        NEXT_PUBLIC_APPEARANCE: ${NEXT_PUBLIC_APPEARANCE:-system}
        NEXT_PUBLIC_DEFAULT_LOCALE: ${NEXT_PUBLIC_DEFAULT_LOCALE:-en}
    image: ugcad-web:local
    ports:
      - "3000:3000"

The localhost fallback belongs in this local override only. Production compose.yml requires NEXT_PUBLIC_APP_URL explicitly.

Step 4 — Start All Services

For a local source build with embedded PostgreSQL enabled:

docker compose -f compose.yml -f compose.local.yml --profile legacy-postgres up --build

To only verify that production GHCR images can be pulled, run:

docker compose up

On first source build this builds the web image (Next.js) and the agents image (LangGraph dev server). Expect 5–15 minutes depending on your connection speed.

The local override uses docker/Dockerfile.agents-dev for the agents service, which runs langgraph dev mode without a paid LangSmith license.

Step 5 — Apply Database Migrations

The web container uses a Next.js standalone build that doesn't include drizzle-kit. Local embedded PostgreSQL should still reuse the repository migration script so it writes public.__ugcad_migrations, matching the production migrations image state mechanism:

POSTGRES_PASSWORD=your_strong_password_here bash scripts/migrate-postgres.sh --compose

Run this once after starting from an empty database. The migration script applies SQL files in apps/web/src/config/db/migrations/meta/_journal.json order; reruns print skipped for recorded migrations and applied for new migrations. Do not use a manual for f in ... psql loop that bypasses public.__ugcad_migrations.

Step 6 — Verify and Access

Check service status:

docker compose ps

When using the compose.local.yml override, confirm ports are bound correctly. You should see 0.0.0.0:3000->3000/tcp and 0.0.0.0:2024->8000/tcp:

NAME                  SERVICE    STATUS    PORTS
<project>-web-1       web        running   0.0.0.0:3000->3000/tcp
<project>-agents-1    agents     running   0.0.0.0:2024->8000/tcp
<project>-postgres-1  postgres   running   5432/tcp
<project>-redis-1     redis      running   6379/tcp

When using the compose.local.yml override, open http://localhost:3000 in your browser.

When using the compose.local.yml override, test the agents API through the local published port:

curl http://localhost:2024/ok
# → {"ok":true}

Troubleshooting

Port shows 3000/tcp without 0.0.0.0: prefix

This port check applies only when using compose.local.yml. Docker Desktop on Windows occasionally fails to bind ports on first container creation. Fix:

docker compose restart web

Agents container fails with "License verification failed"

You are using the licensed langchain/langgraphjs-api image. Switch to the dev Dockerfile in the local override: docker/Dockerfile.agents-dev runs langgraph dev without a license.

Connection refused when web calls agents

The agents container may be binding to IPv6 loopback (::1) instead of 0.0.0.0. Check the dev Dockerfile CMD includes --host 0.0.0.0:

CMD ["npx", "@langchain/langgraph-cli", "dev",
     "--port", "8000", "--host", "0.0.0.0", "--config", "langgraph.json"]

Schema errors on first login

If this is an empty-database initialization and you see errors like column "xyz" does not exist, recreate the test database or test volume and then run the migration step again:

POSTGRES_PASSWORD=your_strong_password_here bash scripts/migrate-postgres.sh --compose

If the database already has tables but public.__ugcad_migrations is empty or out of sync with the journal, read apps/web/src/config/db/migrations/README.md before proceeding. Do not treat a local baseline mismatch as proof that production schema initialization failed.

Updating

Pull new code and rebuild locally with the override:

git pull
docker compose -f compose.yml -f compose.local.yml --profile legacy-postgres up --build -d
POSTGRES_PASSWORD=your_strong_password_here bash scripts/migrate-postgres.sh --compose

Production updates should not build on the server. Let GitHub Actions push new GHCR web, agents, migrations, and rbac-init images, then have Dokploy pull the images selected by GHCR_IMAGE_PREFIX and IMAGE_TAG and run the one-off migrations / rbac-init deployment jobs.