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.
| App | Dockerfile | Port |
|---|---|---|
| Marketing | apps/marketing/Dockerfile | 3001 |
| Docs | apps/docs/Dockerfile | 3002 |
| SaaS | apps/saas/Dockerfile | 3000 |
| API | apps/api/Dockerfile | 3004 |
| MCP | apps/mcp/Dockerfile | 3005 |
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
-
In Workers & Pages, connect this repository to the
a44z-publicWorker using Workers Builds and select the production branch. -
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 rootpackage.json; use Node.js 22.19.0 or newer. -
Under Settings → Builds → Build variables and secrets, enter these values once for the Worker:
Build variable Value 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.
-
Under the Worker's Settings → Variables & Secrets, set marketing's runtime
MARKETING_WAITLIST_WEBHOOK_URL,MARKETING_CONTACT_WEBHOOK_URL, and optionalMARKETING_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. -
Attach the marketing and docs custom domains under Settings → Domains & Routes. Domain attachment remains dashboard-managed. The deploy script derives
DOCS_HOSTNAMEfromNEXT_PUBLIC_DOCS_URL; do not configure that value twice. -
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 docsOmit 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 --buildMCP 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.