Acme
Getting Started

Docker containers

Build the public apps, product app, API, and MCP service.

Each deployable app has its own multi-stage Dockerfile. Run builds from the repository root so Turbo can prune the app and its workspace dependencies.

AppDockerfilePort
Marketingapps/marketing/Dockerfile3001
Docsapps/docs/Dockerfile3002
SaaSapps/saas/Dockerfile3000
APIapps/api/Dockerfile3004
MCPapps/mcp/Dockerfile3005

Build

docker build -f apps/marketing/Dockerfile -t supastarter-marketing .
docker build -f apps/docs/Dockerfile -t supastarter-docs .
docker build -f apps/saas/Dockerfile -t supastarter-saas .
docker build -f apps/api/Dockerfile -t supastarter-api .
docker build -f apps/mcp/Dockerfile -t supastarter-mcp .

The Next.js images accept NEXT_PUBLIC_SAAS_URL, NEXT_PUBLIC_MARKETING_URL, NEXT_PUBLIC_DOCS_URL, and NEXT_PUBLIC_API_URL as Docker build arguments. Supply the deployment URLs before compiling: browser-visible values and the SaaS API rewrite are embedded during the build. Build for the target architecture with Docker's --platform option when it differs from your machine.

Secrets are injected at runtime. Local env files, generated output, and caches are excluded by the root .dockerignore.

Cloudflare: Git-triggered marketing and docs deployment

wrangler.json defines the a44z-public Worker and the marketing/docs containers. tooling/cloudflare/worker.ts forwards requests for the configured docs hostname to docs; other requests go to marketing. Both use a single named instance, with basic sizing and a five-minute idle timeout. An idle container sleeps; the next request starts it again and incurs startup latency.

Configure Workers Builds

  1. In Workers & Pages, connect this repository to the a44z-public Worker using Workers Builds and select the production branch.

  2. Set the root directory to the repository root. Leave the build command empty; set the deploy command to pnpm run deploy:cloudflare. The package manager is pinned in the root package.json; use Node.js 22.19.0 or newer.

  3. Under Settings → Builds → Build variables and secrets, enter these values once for the Worker:

    Build variableValue
    NEXT_PUBLIC_MARKETING_URLRequired HTTPS origin for marketing
    NEXT_PUBLIC_DOCS_URLRequired HTTPS origin on a different hostname
    NEXT_PUBLIC_SAAS_URLOptional HTTPS origin; omit to hide the sign-in link
    NEXT_PUBLIC_API_URLOptional HTTPS origin; accepted by the Dockerfiles but unused by these two apps

    Origins have no path, query, or fragment. No URL values are committed to the Wrangler configuration.

  4. Under the Worker's Settings → Variables & Secrets, set marketing's runtime MARKETING_WAITLIST_WEBHOOK_URL, MARKETING_CONTACT_WEBHOOK_URL, and optional MARKETING_WEBHOOK_SECRET. Store the secret as a secret binding. These settings are independent of build variables and are explicitly forwarded only to the marketing container. Missing webhook URLs cause the corresponding form to report a delivery failure.

  5. Attach the marketing and docs custom domains under Settings → Domains & Routes. Domain attachment remains dashboard-managed. The deploy script derives DOCS_HOSTNAME from NEXT_PUBLIC_DOCS_URL; do not configure that value twice.

  6. Push to the connected production branch. Cloudflare runs the deploy command, builds and publishes both images, and deploys the Worker and containers. Wait for container provisioning, then check both sites, docs search, links, and contact/waitlist delivery.

How variables reach Docker

tooling/cloudflare/deploy.mjs reads the public URLs from the Workers Builds environment, validates them, and inserts the shared image_vars into both container definitions. Wrangler passes those values as Docker --build-arg options to the existing ARG declarations. The script also sets the Worker's DOCS_HOSTNAME binding from the same docs URL.

The script writes .wrangler.deploy.json at the repository root, invokes Wrangler with that configuration, propagates its exit status, and deletes the generated file. It is gitignored. Runtime webhook secrets are not written into this file or passed as image build arguments. keep_vars: true preserves dashboard-managed Worker variables during deployment.

Wrangler builds container images for linux/amd64 automatically. Cloudflare's build infrastructure performs the build; the ARM64 images built on a developer's Mac are not uploaded by this workflow. Both images use the repository root as their Docker build context. Their Dockerfiles already set PORT and HOSTNAME; the Worker classes' defaultPort values match 3001 and 3002.

Public URL changes take effect on the next build/deploy. Runtime environment changes are read when a container starts; a running process retains its startup environment until restarted. The five-minute timeout and instance sizing are editable in worker.ts and wrangler.json, respectively.

Use a separate Worker/environment for staging. Do not configure non-production branch builds to run this production deploy command against a44z-public.

Local Compose

Start only the public apps:

docker compose --env-file .env.local -f docker-compose.app.yaml up -d --build marketing docs

Omit the service names to include SaaS and API. PostgreSQL and S3-compatible storage are managed separately by docker-compose.yml. Set their URLs to hosts reachable from the application containers; localhost inside a container does not refer to the host machine or another container.

API and SaaS receive runtime integration settings from .env.local through Compose's env_file. With another env file, set APP_ENV_FILE to that path and pass it to --env-file as well. This passes custom model-catalog credential names without adding a new Compose mapping for each provider.

Marketing receives its three webhook settings explicitly. See Marketing for contact and waitlist delivery configuration.

AI and speech packaging

API and MCP use app-local build.mjs scripts, invoked by pnpm build, to bundle their servers and copy runtime resources into dist/.

The API image retains its production dependency tree for Sharp native binaries and Pi SDK resources. Its dist/ contains the server bundle, prompt Markdown, and merged shared/agent-only skills. The API starts the embedded agent; the SaaS image does not start a second runtime.

SaaS production builds use tsconfig.build.json to exclude tests that import other apps outside its pruned dependency graph. The normal workspace pnpm type-check still checks those tests.

The SaaS build generates public/speech/pcm-worklet.js, and the Dockerfile copies that public directory into the standalone image. Preserve WebSocket upgrades for /api/speech/stream in any proxy placed in front of the apps.

API /api/ready checks database connectivity, and SaaS /ready checks the API. These are not checks of model-provider credentials or agent storage availability.

MCP

Set MCP_ENABLED=true and configure MCP_SERVER_URL, NEXT_PUBLIC_SAAS_URL, and the database connection as described in MCP setup, then enable the Compose profile:

docker compose --env-file .env.local -f docker-compose.app.yaml --profile mcp up -d --build

MCP ships its shared skills and external QuickJS/schema-generation dependencies, including WASM assets. It does not load agent-only skills. The same backend env file enables the corresponding OAuth plugin in API/SaaS.

Apply database migrations as a separate deployment step using pnpm --filter @repo/database migrate:deploy; image builds do not connect to a database or apply migrations.

On this page