Deploy

Let's install AstralBeam on a Linux host, with PostgreSQL behind a connection pooler and a reverse proxy in front. Basic knowledge of Linux, systemd, and TLS termination is assumed here. Overview describes what the finished deployment consists of.

1. Get a release binary

Every tagged release from v0.13.0 publishes prebuilt platform assets named astralbeam-platform-<target>, where <target> is linux-x86_64, linux-arm64, macos-x86_64, macos-arm64, or windows-x86_64.exe. The astralbeam-<target> assets beside them are the CLI, not the server. Earlier releases put the version in each asset name, such as astralbeam-platform-v0.12.2-linux-x86_64.

Run these commands to download the latest x86_64 asset with the release's SHA256SUMS, check the download against it, and install it as astralbeam-platform. On an arm64 host, replace linux-x86_64 with linux-arm64:

curl -fsSLO https://github.com/AstralBeamAI/astralbeam/releases/latest/download/astralbeam-platform-linux-x86_64
curl -fsSLO https://github.com/AstralBeamAI/astralbeam/releases/latest/download/SHA256SUMS
sha256sum --check --ignore-missing SHA256SUMS
chmod +x astralbeam-platform-linux-x86_64
mv astralbeam-platform-linux-x86_64 /usr/local/bin/astralbeam-platform

TIP: To pin a release instead, replace latest/download with download/v<version> in both URLs, for example download/v0.13.4. Releases before v0.13.4 publish no SHA256SUMS.

sha256sum prints astralbeam-platform-linux-x86_64: OK when the download matches the release's checksum and fails otherwise. That proves the file arrived intact, but the release carries no signature, so nothing here verifies where it came from. Running the binary is only a smoke check. astralbeam-platform version prints the release version and target, such as astralbeam-platform 0.13.4 (linux-x86_64), and astralbeam-platform --help lists every command. With the two bootstrap variables set, it must answer GET /api/status with {"status":"ok"} and exit on SIGTERM. CI smoke-tests the linux-x86_64 binary the same way, without the database-backed status check, and cross-compiles the other targets unrun.

For a fork, or a target without a prebuilt asset, we build the binary ourselves. Deno is the only supported toolchain.

With Deno installed, run these commands from the repository root to install the frozen dependencies, build the SDK the platform bundles, and build the binary:

(cd sdk && deno install --frozen)
deno task --cwd sdk build
(cd platform && SHARP_IGNORE_GLOBAL_LIBVIPS=1 deno install --frozen)
deno task --cwd platform build

NOTE: Do not run scripts/setup.sh on a deployment host. It is the contributor setup, and it migrates any database DATABASE_URL reaches and seeds it with fixed development credentials.

The binary lands at platform/.output/astralbeam-platform. build writes the Nitro server bundle and static assets into platform/.output, then compiles them into the binary.

Run this command to smoke-test the binary the way CI does:

deno task --cwd platform binary:check

It rejects a binary over 200 MiB, requires version and --version to print the platform version and --help to list migrate and upgrade, starts it on a free loopback port, and requires the status endpoint, the built stylesheet, /api/openapi.json with its cache and CORS headers, a docs page that revalidates with an ETag, and a clean exit within 5 seconds of SIGTERM. With BINARY_CHECK_DATABASE_URL pointing at an empty database, as in CI, it also requires migrate --dry-run to list every embedded migration and leave no tables behind. It prints Binary smoke check passed with the binary's size when all of that holds.

TIP: You can skip the binary and run the same commands with deno task --cwd platform start, which needs the repository, its installed dependencies, and a fresh build on the host.

2. Provision the database

PostgreSQL 18 is the minimum. Production deployments should connect through a dedicated account with limited privileges, instead of the postgres superuser.

Run these commands to create that role and an empty database it owns:

createuser --pwprompt astralbeam
createdb --owner=astralbeam astralbeam

The application owns its schema from there. The first migration creates the citext extension, so the role must be permitted to run CREATE EXTENSION.

Point DATABASE_URL at a transaction-pooling pooler rather than at PostgreSQL directly. The application sends prepared queries, so we must give the pooler a prepared statement allowance as well. With PgBouncer that means pool_mode = transaction and a non-zero max_prepared_statements, which the reference Compose setup sets to 200.

NOTE: A pooler in transaction mode without a prepared statement allowance fails queries as soon as a statement is prepared.

3. Set the bootstrap environment

Only two variables are required. Both are read once per process, so changing either needs a restart.

VariableRequiredNotes
DATABASE_URLYesPostgreSQL connection URL, for example postgresql://user:password@host:5432/database
DATABASE_ENCRYPTION_KEYYesComma-separated keyring. Each entry is 32 to 1024 characters and unique. The first entry encrypts new writes and is the operator sign-in credential
PORTNoTCP port to listen on
APP_BASE_URLNoEnvironment override for the base URL setting, which can otherwise be set at /configure
CLUSTER_RUNNER_HOSTNoPrivate hostname or IP advertised to peer runners. Defaults to 127.0.0.1
CLUSTER_RUNNER_PORTNoPrivate runner port. Defaults to 0, which publishes an OS-assigned port after binding
CLUSTER_RUNNER_LISTEN_HOSTNoBind interface for the private runner listener. Defaults to CLUSTER_RUNNER_HOST

Each process starts an embedded cluster runner and lets Effect initialize or migrate its effect_cluster_* tables automatically. The runtime database role needs USAGE and CREATE on the target schema and ownership of existing cluster tables for upgrades. See cluster operations. For replicas on different machines or isolated container networks, set an individually reachable private runner host and port for each process. Peers must reach that address directly. Use a fixed port where firewall rules or container mappings require one. A wildcard listen host such as 0.0.0.0 still needs an explicit, reachable advertised host.

Keep runner ports on a trusted private network because they expose internal cluster RPCs. Do not forward them through the public reverse proxy or advertise a shared load balancer address. These settings are environment-only and changing them requires restarting the process.

When either database variable is missing from the environment, astralbeam-platform reads it from ~/.astralbeam/platform.json. Run interactively, it prompts for any variable still missing and saves the answer there, readable only by you. At startup it prints whether each value came from the environment or that file, and the environment always wins.

Run this command to generate a high-entropy encryption value:

openssl rand -base64 32

Keep it in your deployment's secret manager. The keyring is hashed into key material, so length and hashing do not rescue a weak passphrase, and losing the first entry means losing every stored secret.

A rejected value reports DATABASE_ENCRYPTION_KEY must be a comma-separated list of unique secrets containing 32 to 1,024 characters each, and a missing URL reports 'DATABASE_URL' environment variable is not set. With either variable missing or invalid, /configure renders a "Server restart required" page naming the offending variables instead of the editor.

4. Start the server

To keep the server running even after we log out of the host, we must set it up as a Linux system service, owned by an unprivileged user.

Save this unit as /etc/systemd/system/astralbeam-platform.service:

[Unit]
Description=AstralBeam
After=network-online.target

[Service]
User=astralbeam
EnvironmentFile=/etc/astralbeam/env
Environment=PORT=3000
ExecStart=/usr/local/bin/astralbeam-platform
Restart=on-failure

[Install]
WantedBy=multi-user.target

Keep DATABASE_URL and DATABASE_ENCRYPTION_KEY in the EnvironmentFile rather than in the unit, and restrict that file to the service user.

Run these commands to load the unit and start the service:

systemctl daemon-reload
systemctl enable --now astralbeam-platform

systemctl status astralbeam-platform should now report the service as active, and curl -i http://127.0.0.1:3000/api/status should answer {"status":"ok"}. The process logs to stdout and stderr, which systemd captures, and exits on SIGTERM, so systemctl restart and systemctl stop are both clean.

NOTE: The development server runs on port 4500. A production process listens on whatever PORT says.

5. Put it behind a reverse proxy

Terminate TLS at the proxy, bind the application to loopback, and let nothing else reach the origin. /configure trusts X-Forwarded-Host and X-Forwarded-Proto only when the request peer is a loopback address, so the proxy must run on the same host as the application, must overwrite both headers, and must not be bypassable.

Add this location block to the server that terminates TLS:

location / {
  proxy_pass http://127.0.0.1:3000;
  proxy_set_header Host $host;
  proxy_set_header X-Forwarded-Host $host;
  proxy_set_header X-Forwarded-Proto $scheme;
  proxy_set_header X-Forwarded-For $remote_addr;
}

The application replaces X-Forwarded-For with the real peer address whenever the peer is not loopback, so a client cannot spoof the address authentication rate limits are keyed on.

The encryption key grants access to every encrypted value in the database, so /configure deserves more protection than the application's own throttle. Restrict who can reach it at the ingress with an IP allowlist, a VPN, or an identity-aware proxy, and rate-limit it there during first setup. See Security.

6. Complete setup

Open https://your-host/configure in a browser. Every other page redirects there until setup is complete.

  1. Sign in with the first entry of DATABASE_ENCRYPTION_KEY. Database credentials and fallback keyring entries are not accepted, and a wrong value reports Invalid encryption key.
  2. On a new database, the page shows the pending migrations with their SQL. Expand and review them, then apply them. See Operations.
  3. Fill in the required settings: the application base URL, the Cloudflare Turnstile site and secret keys, and the authentication secret, which is generated for you on the first save if you leave it unset. Add email delivery and OAuth clients as needed. Each organization configures its model connections in Models, then assigns enabled models to its agents. Every deployment setting is described in Configuration.
  4. When the page reports "Configuration is complete", use Go to app, which ends the operator session and loads the application.

Sessions last 15 minutes and sign-in is throttled to 5 attempts per minute, so keep the key at hand while you work through the form.

7. Verify the deployment

Run each of these against the public origin:

CheckExpected
curl -i https://your-host/api/status200 with {"status":"ok"} and X-Content-Type-Options: nosniff
curl -sI https://your-host/docs200 with Cache-Control: public, no-cache and an ETag
curl -s https://your-host/api/openapi.jsonThe OpenAPI document, including the /api/v1/tenants path
curl -i https://your-host/api/v1/tenants401 once setup is complete, 503 with Retry-After while it is not

/api/status counts organization rows and answers {"status":"ok"} when that query succeeds. A database failure returns 503 with an error field, without the count. There is no separate readiness endpoint, so to check API readiness, call an API route and treat 503 as not ready.

Upgrade

Let's move a running deployment to a newer release. Read the release notes first, and back up the database before an upgrade that carries migrations.

  1. Run this command to replace the installed binary with the latest release for this host's target:

    sudo astralbeam-platform upgrade

    To pin a release instead, pass its tag, as in sudo astralbeam-platform upgrade v0.13.4. The command downloads the matching astralbeam-platform-<target> asset from GitHub, checks it against the release's SHA256SUMS, and swaps it in place after you confirm. It refuses a download that does not match its checksum or is missing from the file, and it supports releases from v0.13.4, the first to publish SHA256SUMS. Pass -y or --yes to skip the confirmation, which a run without a terminal needs because it cannot answer the prompt. It needs sudo only because /usr/local/bin belongs to root. Run from a checkout with deno task start, it refuses, because the running executable is then Deno itself, so pull and rebuild the checkout instead. Like the manual download, it verifies no signature.

  2. Run this command with the new binary to apply the release's migrations, as described in database commands:

    astralbeam-platform migrate

    It prompts for DATABASE_URL when neither the environment nor the saved file has it. You can skip this step and apply them from /configure after the restart instead, where the gate redirects every page until you review and apply the new SQL.

  3. Run this command to restart the service, since the running process keeps serving the old binary until then:

    systemctl restart astralbeam-platform
  4. Upgrade and restart every other replica so each one reloads configuration and migration state.

Downgrading is not supported, because a migration has no rollback. upgrade refuses an older tag, and without a tag it reports Already at <version> when the running binary is newer than the latest release. Reverse a schema change with a forward migration instead.