Docs · Get started

Install on your own server

One command writes a Docker Compose stack to your server, starts it, runs the migrations and checks that everything answers. The admin and the gateway run signed, prebuilt release images.

What you get

A Docker Compose stack in ./.caelo/ with four services:

  • Postgres 16 with both Caelo databases.
  • Caddy, which fetches TLS certificates from Let's Encrypt and serves the live site on <domain> (with /api/* going to the gateway), the admin on admin.<domain> and the staging site on its own host with X-Robots-Tag: noindex.
  • The admin and the API gateway, from ghcr.io/caelo-cms/admin and ghcr.io/caelo-cms/gateway.

The images are pinned by digest and verified with cosign against Caelo's release workflow before anything is written. You never build an image, and a docker compose pull cannot change the release behind your back.

Requirements

  • A Linux server with Docker Engine and the Compose plugin.
  • Bun 1.3.13 or newer.
  • cosign on the PATH. Without it the install stops before writing anything.
  • Ports 80 and 443 reachable from the internet, and DNS records for three hosts pointing at the server: <domain>, admin.<domain> and staging.<domain> (or the host you pass as --staging-domain).
  • Enough memory: the admin container reserves 2 GiB for the quality checks, on top of Postgres and Caddy.
  • For installing updates from the admin: systemd, and a non-root user in the docker group to run the install as.

Install

Run this from the directory that should hold the install:

bunx @caelo-cms/provisioning@latest init --provider self-hosted \
  --domain example.com \
  --owner-email you@example.com

The same command without init does the same thing. In order, it:

  1. resolves the admin and gateway images of its own Caelo version to digests and cosign-verifies them. A failed check stops here, and nothing is written. --version x.y.z installs another release;
  2. generates the secrets and writes them to .caelo/config.json, and records the verified digests in .caelo/release.json;
  3. writes .caelo/docker-compose.yml and .caelo/Caddyfile;
  4. on Linux with systemd, sets up the update agent (see Updates and maintenance);
  5. prints the owner setup link, valid for 24 hours;
  6. pulls the images, starts Postgres, runs the migrations, starts everything and waits for the admin and gateway health checks;
  7. prints the DNS hosts to point at the server and the setup link again.

Open the setup link (https://admin.<domain>/setup?token=…) to create the owner account, then choose your AI provider. See the Quickstart.

If a step fails

The installer names the step and the reason. Fix the cause and run, from the same directory:

bunx @caelo-cms/provisioning@<version> up

up re-checks the database roles, re-writes the Compose file and the Caddyfile, and brings the stack up from wherever it stopped. Run it too after you change anything in .caelo/config.json. The setup link is printed before the stack starts, so a failed start does not lose it.

Options

FlagUse it when
--tls internalThe server has no public DNS (a LAN box, an evaluation install). Caddy issues certificates from its own local CA; browsers warn until you trust its root.
--http-port, --https-portPorts 80 and 443 are taken. Let's Encrypt then only works if another proxy forwards those hosts to Caddy, so combine them with --tls internal or such a proxy.
--staging-domainStaging should run on another host than staging.<domain>.
--version x.y.zYou want a specific release.
--skip-verifyOnly for forks that publish unsigned images. Never on a production install.

Files that matter

  • .caelo/config.json (mode 600): database passwords and the key that encrypts stored AI provider keys. Backups do not contain it. Keep a copy somewhere safe.
  • .caelo/release.json: the running release, its image digests and the upgrade history.
  • .caelo/docker-compose.yml and .caelo/Caddyfile: generated. up rewrites them.

Day-to-day

TaskCommand
Restartdocker compose -f .caelo/docker-compose.yml restart
Logsdocker compose -f .caelo/docker-compose.yml logs -f caelo-admin caelo-gateway caddy
Container healthdocker compose -f .caelo/docker-compose.yml ps
Apply migrations by handbunx @caelo-cms/provisioning@<version> migrate
Upgrade, back up, restoreSee Updates and maintenance

Redirects that Caelo records are answered by Caddy. Each build carries a redirects file that Caddy imports and reloads within seconds of a deploy.

Limits

  • One server, one Postgres. There is no failover and no read replica.
  • Backups are point-in-time archives (pg_dump plus files), not continuous WAL archiving. Schedule them yourself.
  • There is no rotate-secret or destroy command for self-hosted installs yet.

Common problems

  • "cosign is required … was not found on PATH". Install cosign and re-run.
  • "cosign verify FAILED". The image was not signed by Caelo's release workflow. Do not continue. Retry once in case of a Sigstore outage.
  • "Could not resolve ghcr.io/caelo-cms/admin:<version>". The version does not exist (check the releases) or GHCR is unreachable.
  • Certificates are not issued. The three hosts must resolve to the server and ports 80 and 443 must be reachable.
  • An install made with an older release never came up. Run bunx @caelo-cms/provisioning@latest up once from the directory that holds .caelo/. It rewrites the Compose file and the Caddyfile and removes services older releases referenced. The admin is then on admin.<domain>.

Something wrong or missing on this page? Open an issue on GitHub