Self-Hosting

Production Checklist

What to verify before taking an Appstrate instance live.

The defaults suit local development. Going live takes a few deliberate decisions. Walk through this list before you open the instance to real users. Variables are described in Environment Variables, and appstrate install already does several of these steps for you.

Choose the stack

  • Single node: Tier 2 (PostgreSQL and Redis, files on a persisted volume) with RUN_ADAPTER=docker. This is the installer's recommended stack.
  • Several platform instances, or object storage: add S3-compatible storage (S3_BUCKET, S3_REGION, S3_ENDPOINT for MinIO or R2) and point every instance at the same PostgreSQL, Redis and bucket. Bundled MinIO is Tier 3.
  • Hostile or multi-tenant workloads: consider RUN_ADAPTER=firecracker for a microVM per run.
  • Do not use Tier 0, Tier 1 or RUN_ADAPTER=process in production. They give up persistence, shared state or isolation. See Progressive Infrastructure.

Terminate TLS and set the public URL

Appstrate does not terminate HTTPS. Put a reverse proxy in front (nginx, Caddy, Traefik, or a cloud load balancer) and set:

APP_URL=https://appstrate.example.com
TRUSTED_ORIGINS=https://appstrate.example.com
TRUST_PROXY=1

With NODE_ENV=production, APP_URL must be HTTPS (loopback excepted), and an HTTPS APP_URL with TRUST_PROXY=false refuses to boot. TRUST_PROXY is the number of proxies that append to X-Forwarded-For, and a value higher than your topology lets callers spoof their IP. Raise the proxy's body limit above your largest upload (100 MiB by default) and do not buffer or compress event streams. See Docker Compose.

Generate and protect the secrets

Five secrets are required, and each rotates independently:

VariableFormatImpact of changing it
BETTER_AUTH_SECRETRandom stringChanging it in place leaves what was encrypted under it (the keys that sign CLI and OIDC tokens) unreadable. Rotate through the BETTER_AUTH_SECRETS keyring instead, which signs everyone out
CONNECTION_ENCRYPTION_KEY32 bytes, base64Stored credentials become unreadable unless you rotate with a key id. See the rotation procedure in Environment Variables
UPLOAD_SIGNING_SECRETAt least 16 charactersInvalidates in-flight upload tokens
RUN_TOKEN_SECRETAt least 16 charactersInvalidates tokens of in-flight runs
CONNECT_SESSION_SECRETAt least 16 charactersInvalidates in-flight connect sessions

The installer generates them. If you do it by hand, use openssl rand -hex 32 for the first four and openssl rand -base64 32 for the encryption key. Keep them in a secret manager, never in git, and keep an offline copy of CONNECTION_ENCRYPTION_KEY: without it, no backup of the database can decrypt the credentials it holds.

Close the instance

Open mode lets anyone with the URL create an account and an organization. For a private deployment, run in closed mode: AUTH_DISABLE_SIGNUP, AUTH_DISABLE_ORG_CREATION, AUTH_PLATFORM_ADMIN_EMAILS and a one-time AUTH_BOOTSTRAP_TOKEN, claimed at <APP_URL>/claim. The installer sets this up when you give it an owner email or run unattended. Then:

  • Remove AUTH_BOOTSTRAP_TOKEN from .env once the instance is claimed.
  • Check that you can sign in to every address listed in AUTH_BOOTSTRAP_OWNER_EMAIL and AUTH_PLATFORM_ADMIN_EMAILS.
  • If your Compose file predates 1.0.0-beta.65, confirm it forwards AUTH_BOOTSTRAP_TOKEN to the container. The current files do (see Docker Compose).

The full recipes and pitfalls are in AUTH_MODES.md.

Restrict CORS

TRUSTED_ORIGINS is a comma-separated list of the exact origins of your dashboards and integrations. The shipped Compose files leave it empty, and an empty value falls back to http://localhost:3000,http://localhost:5173, so set it explicitly.

Review the limits

The defaults are conservative: 200 run launches per minute and 50 concurrent runs per organization, a 30 minute run ceiling, and 1536 MiB and 2 vCPU per run. Override only what you need, in PLATFORM_RUN_LIMITS and INLINE_RUN_LIMITS. Lower timeout_ceiling_seconds if your agents never need 30 minutes. Pair rate limiting with Redis, otherwise limits are per process and reset on restart. See Rate Limits.

Pin image versions

Pin one release for every image:

APPSTRATE_VERSION=1.0.0-beta.65

Tags have no v prefix. APPSTRATE_VERSION is a Compose variable that moves the platform, the agent runtime, the sidecar and the MCP runner images together, and the platform refuses to boot if the platform and runtime image versions disagree. If you run your own Compose file, pin all of them yourself. Read Upgrading before changing the pin.

Configure model providers

Appstrate needs at least one LLM provider. Either set SYSTEM_PROVIDER_KEYS (a JSON array of providers with their models, injected by the sidecar and never exposed to agents) or let organizations add their own. A model on a private endpoint needs its host in EGRESS_ALLOW_INTERNAL_HOSTS.

Review egress

Agents can only reach hosts that an integration's authorized_uris allows, and private ranges are blocked. If an integration must call an internal API, add its host to EGRESS_ALLOW_INTERNAL_HOSTS. That is an instance-wide trust decision, so list only hosts that every organization may use. The sidecar needs working DNS. See Isolation and Security.

The API process itself also makes outbound requests to get.appstrate.dev to read the live model catalog. On a network that must stay closed, set MODEL_CATALOG_URL=off (see Isolation and Security).

Configure email

Email verification and magic-link sign-in need SMTP. Set SMTP_HOST, SMTP_USER, SMTP_PASS and SMTP_FROM together (SMTP_PORT defaults to 587): mail is enabled only when all four are set, and no error is raised when they are not. Test with a magic link or a password reset before go-live.

With SMTP on, the instance also mails account notices: a password change or reset is reported to the account's address, a sign-up with an address that already has an account is answered at that address, and an email change must be approved from the current address before anything is sent to the new one. Without SMTP, an email change applies at once, with no confirmation.

The Compose files shipped since 1.0.0-beta.65 forward the SMTP and the Google and GitHub sign-in variables. The tier templates of earlier releases do not, so with an older file check that they are listed under appstrate.environment (see Docker Compose).

Choose your modules

MODULES defaults to oidc,webhooks,mcp,core-providers,@appstrate/module-chat. Drop what you do not use to reduce the attack surface, but keep oidc if you use the CLI. Opt-in modules (firecracker, @appstrate/module-observability, the two subscription providers, @appstrate/module-ee) are listed in Progressive Infrastructure.

Protect the Docker socket

The docker backend needs the socket, and access to it is root on the host. Keep it root:docker 0660, run the platform with the host's docker group (DOCKER_GID in the tier templates), and read the socket-proxy notes in the root docker-compose.yml. See Isolation and Security.

Set up monitoring

  • Health. GET /health returns healthy, degraded, unhealthy or starting with per-subsystem checks, answering 503 when the database is unhealthy or the server is still starting. GET / serves the dashboard and is not a health signal.
  • Logs. The platform writes structured JSON to stdout. LOG_LEVEL=info is the default. Use debug during incidents (it adds one access line per request), and warn for steady state. Forward the logs to your stack and correlate on the Request-Id response header.
  • Traces and metrics. Append @appstrate/module-observability to MODULES and set OTEL_EXPORTER_OTLP_ENDPOINT to export OpenTelemetry over OTLP/HTTP. It is off by default. See OBSERVABILITY.md.
  • Log levels and messages change between releases. Check the operator notes before you rely on a log line for an alert.

Plan backups

Appstrate has no built-in backup tool. Use your existing playbook, and test a restore.

  • PostgreSQL: a nightly logical dump (pg_dump -Fc), kept off the host. This holds your users, agents, runs and encrypted credentials.
  • Secrets: CONNECTION_ENCRYPTION_KEY and the other secrets, stored separately from the database dump.
  • Files: the storagedata volume on Tier 1 and 2, or the S3 bucket (versioning and replication) on Tier 3 and with your own S3. On Tier 3 the bundled MinIO volume is miniodata.
  • Redis: holds the queue behind scheduled runs, webhook deliveries and other background jobs, plus run-event buffers. The Compose files persist it on a volume. Keep that volume if losing queued work matters, or use a replicated Redis.
  • A one-off script, scripts/storage-orphans.ts, reconciles stored objects against database rows. Run it in dry-run mode first.

Set LEGAL_TERMS_URL and LEGAL_PRIVACY_URL to show your policies in the footer.

Plan upgrades

Migrations run at boot, there is no built-in rollback, and some releases need operator steps before the deploy. Pin versions, rehearse in staging, take a database backup and read the operator notes of every release you skip. See Upgrading.

Sanity check before go-live

# 1. Health
curl https://appstrate.example.com/health

# 2. Auth is enforced (expect 401)
curl -i https://appstrate.example.com/api/agents

# 3. OpenAPI is served
curl https://appstrate.example.com/api/openapi.json | jq .info

Then, in the dashboard: sign in as the owner, add a model, run a trivial agent end to end, and confirm a signed-out visitor cannot create an account (in closed mode). If the three commands answer as expected and the agent run succeeds, you are production-ready.

On this page