[data-reveal]{opacity:1!important;transform:none!important}
Building

Deployment

Run the DMS in production — build both processes, start them in the right order, and check off the secrets and multi-instance requirements.

Production runs the same two processes as development — the backend and the acms frontend (Architecture) — but each is built once and launched from its build artifacts instead of the dev tooling. The steps below are the production-specific ones; the pages linked from each section own the details.

Build and run the backend

Build the project once, then launch from the artifacts:

Terminal
ajs project build
ajs project start

ajs project start launches from the build output and skips module download and graph validation. ajs project dev (and ajs project run, its legacy alias) runs the project in dev mode even without -w — so don't use either as a production launcher.

Use production values in your antelope.config.ts: real secrets (see the checklist below), Redis for realtime, durable file storage, and your SMTP settings. Every key and default is in Configuration.

Build and run the frontend

The frontend is built against a running backendacms build downloads the resolved Nuxt layers from it — then served from the build output:

Terminal
export CMS_BOOTSTRAP_SECRET=# same value as the backend's nuxt.bootstrapSecret
acms build -b https://api.example.com
acms start -b https://api.example.com -p 3001

Neither command auto-discovers the backend: pass -b or set CMS_BACKEND_URL. The URL also selects the workspace under ~/.acms/, so start must receive the same URL as build. Flags, workspace behavior, and the --offline cached-manifest mode are in Frontend CLI.

CMS_BOOTSTRAP_SECRET is what entitles the build host to the layers and their private options. Without it the build still succeeds, but the resulting frontend has no oauth.relaySecret or htmlRender.serviceSecret — provider login and server-side HTML rendering then fail at runtime. Both ends say so: acms build warns that no layer arrived with private options, and the backend logs the same conclusion. Set nuxt.requireBootstrap: "enforce" to make the build fail instead. start does not need the secret.

Pass it through the environment rather than --bootstrap-secret, and note that a credential read from a file keeps its trailing newline — acms rejects a value that cannot travel in an HTTP header rather than letting fetch fail obscurely.

Startup order

Start the backend first and wait until it is ready; the frontend asks it for the layers at startup (Architecture). The same ordering applies to the build step: the backend must be up and serving your final module set before you run acms build, or the built frontend won't match it.

Rebuild the frontend whenever the layer set changes — you add or remove a module that calls AddNuxtLayer, or you change a layer's code. Backend-only changes (pages, components, data) need no frontend rebuild: pages are data the frontend draws.

Production checklist

The defaults are development values. Set each of these before going live:

KeyDefaultProduction requirement
auth.jwtSecret (DMS config)""A strong random value — it signs every session token.
htmlRender.serviceSecret (DMS config)"dev"A strong random value — it signs the render-service tokens.
nuxt.bootstrapSecret (DMS config)A strong random value — it gates the layer manifest and source archive. Also set it as CMS_BOOTSTRAP_SECRET where you run acms build.
secret (the @antelopejs/auth-jwt module's config)A strong random value, independent of auth.jwtSecret.
apiBaseUrl / clientBaseUrllocalhostYour public backend and dashboard URLs — generated links (invite emails, signup) use them.
htmlRender.renderEndpointlocalhostThe production frontend's /api/html/render URL, reachable from the backend.

All of these live in the DMS config block except the auth-jwt secret, which belongs to that module's own entry — Configuration documents each key. Transactional email also needs an interface-email implementation (typically @antelopejs/nodemailer) wired in the config, or invites, validation, and password resets fail (Backend services).

Multiple instances

Running more than one backend instance adds two requirements:

  • Redis for realtime. The in-memory driver does not cross process boundaries — events, presence, and notification streams stay on one instance. Load @antelopejs/redis and keep realtime.driver at "auto" or set "redis" (Configuration).
  • Locks around scheduled work. Cron jobs fire on every instance, so wrap fleet-wide work in runWithLock — the lock is database-backed, so it needs no extra infrastructure (Backend services).
A single-instance deployment works without Redis, but the DMS uses it when present (realtime.driver: "auto"), so most production stacks include it from the start.