Skip to main content

Docker Compose (deprecated)

For a new install, use the Helm chart on Kubernetes. On one Linux host, follow Install on one host (k3s).

This page installs the Docker Compose stack in docker/self-host/ on one Linux host. It stays supported for the rest of the 0.x releases. From 1.0.0, the Helm chart is the only supported deployment. Other setups (own reverse proxy, external database, Podman) can work but are unsupported.

The stack you get:

ServiceWhat it is
reverse-proxyTraefik — TLS through Let's Encrypt, routes /, /api, /webhooks
webappReact SPA served by nginx
application-serverSpring Boot API
application-workerSame image, worker profile — runs the AI sandboxes. The only service given the Docker socket
webhook-serverSame image, webhook profile — keeps receiving events while the app restarts
postgresPostgreSQL 18 + pg_partman (custom image — vanilla postgres:18 is not a drop-in)
nats-serverNATS JetStream event buffer
CAUTION: Docker Compose is deprecated

The compatibility policy states the supported deployment from 1.0.0 and what it means for your install.

Requirements​

Supported host matrix​

Hephaestus supports the specified single-host stack on the following native (not emulated) hosts:

DistributionCPU architecture
Ubuntu 24.04 LTSx86-64 (amd64)
Ubuntu 24.04 LTSARMv8 64-bit (arm64)

Both require the Docker Engine and Docker Compose versions in Tested versions. Docker supports these hosts according to its Ubuntu installation requirements. Before publication, each release passes qualification on both matrix cells.

Only the listed native hosts and the unmodified self-hosted Compose topology are supported. Other hosts, runtimes, and topologies are outside release qualification.

Tested versions​

This table lists the versions that CI and release qualification actually run. A version that no job runs is not in the table.

ComponentVersionWhat tests it
Docker Engine28 or newerRelease qualification refuses an older engine on both matrix cells. It records the engine that it ran in the release assets host-smoke-amd64.json and host-smoke-arm64.json. No job runs version 28.0.0 itself.
Docker Compose2.24.4 or newerRelease qualification refuses an older Compose and records the version that it ran in the same assets. Every pull request checks that the Compose files render. No job runs version 2.24.4 itself.
PostgreSQL18, the release image with pg_partmanThe merge queue runs the integration tests against an image built from the same Dockerfile before every change merges.
NATS serverThe version that security/release-images.json pinsRelease qualification boots it. The integration tests use NATS 2.10.
TraefikThe version that security/release-images.json pinsRelease qualification sends a request through it.
Java21CI builds and tests with Java 21. The server image runs the Java 21 runtime of its buildpack.
BrowsersChromium, the build that Playwright 1.63 bundlesThe end-to-end, component, and Chrome extension suites run Chromium only. No job runs Firefox or Safari.
Chrome extensionChrome 130 or newerThe extension manifest requires it. No job runs Chrome 130 itself.
Node.js, for release-lock verificationThe version pinned in package.json#devEngines.runtimeCI installs this version through the setup-toolchain action.
Cosign, for release-lock verification3.1.4Release, promotion, and image signing jobs verify with this version.

To see which Docker Engine and Docker Compose a release ran, download the host-smoke-*.json assets of that release from the releases page.

  • The CPU, memory, and disk that Sizing gives.
  • A host in the supported matrix, plus git and openssl.
  • An NTP-synchronized clock. Clocks explains why.
  • A DNS A record that points your hostname at the host. Ports 80 and 443 must be reachable from the internet for Let's Encrypt HTTP-01, OAuth callbacks, and webhooks.
  • Outbound HTTPS to the destinations in Network egress.
  • The Node.js version pinned in package.json#devEngines.runtime and Cosign for release-lock verification.

Clocks​

Keep the clock of every host that runs a Hephaestus role or PostgreSQL synchronized by NTP. Conversation turn leases and person erasure compare times that different hosts write. If Slack does not answer when Hephaestus starts a streamed reply, the server looks for that reply in the thread by time. Hephaestus assumes that Slack's clock and the server's clock differ by less than 2 seconds. Conversation runtime § Slack replies has the rule.

1. Get the files​

  1. Check out the newest version from the releases page:
VERSION=0.95.0 # the release you are installing, without the leading "v"
sudo git clone --depth 1 --branch "v$VERSION" \
https://github.com/hephaestus-build/Hephaestus.git /opt/hephaestus
sudo chown -R "$USER" /opt/hephaestus
cd /opt/hephaestus
cd docker/self-host
./setup.sh
cd ../..
node scripts/prepare-release-lock.ts "v$VERSION"
cd docker/self-host

setup.sh creates or updates .env with mode 0600. It generates the database password and master encryption key (HEPHAESTUS_SECURITY_ENCRYPTION_KEY). It copies the master key into an empty HEPHAESTUS_SECURITY_CREDENTIAL_ENCRYPTION_KEY. It also generates these values:

  • OAuth state-cookie key.
  • Webhook secret.
  • Routing secret.
  • Message-broker credentials. It does not replace non-empty values and never prints a secret. Changing either encryption key on an existing installation makes everything encrypted under the old one unreadable.

Everything below runs from /opt/hephaestus/docker/self-host. After step 2 supplies the remaining required values, docker compose --env-file .env --env-file release-lock.env config renders the effective configuration. Before then it exits at the first missing required variable, as does every other docker compose subcommand in this directory. See Troubleshooting.

2. Configure .env​

CAUTION: Back up both encryption keys

HEPHAESTUS_SECURITY_ENCRYPTION_KEY protects the values that What the database seals lists. HEPHAESTUS_SECURITY_CREDENTIAL_ENCRYPTION_KEY protects integration credentials. Loss of either key makes the corresponding database values unreadable. Keep both with the backup set, in separate storage with separate credentials, as Keep the required keys outside the cluster describes. Rotate the credential key only with the credential-key rotation procedure.

setup.sh manages the internal secrets listed above.

  1. Open .env.

  2. Fill in the remaining REQUIRED values.

  3. Do not replace the generated internal secrets manually.

    VariableWhat / how
    APP_HOSTNAMEYour public hostname, for example, hephaestus.example.com
    ACME_EMAILLet's Encrypt expiry notices
    One login providerGitHub OAuth (GH_OAUTH_CLIENT_ID / _SECRET) or GitLab OAuth (GITLAB_OAUTH_CLIENT_ID / _SECRET and its base URL), from First steps § 1. One is enough.
    DOCKER_GROUP_IDHost group ID that can access /var/run/docker.sock. Use the command below. A wrong value prevents every sandbox start.
    HEPHAESTUS_AUTH_BOOTSTRAP_ADMINSFrom First steps § 2 — set it before first boot
  4. To find DOCKER_GROUP_ID, run:

    stat -c %g /var/run/docker.sock

    Other generated values also affect the lifecycle:

    • A database password change does not update an initialized PostgreSQL volume.
    • State-cookie key rotation invalidates OAuth flows in progress.
    • Webhook secret rotation requires the same change at each configured provider. The GitLab routing secret is never entered anywhere. GitLab group hooks stop delivery until registration repeats if you rotate the secret incorrectly.
  5. Rotate it as production setup describes.

  6. Back up .env with the database as Backup & Restore describes.

Serving more than one hostname​

An instance answers on APP_HOSTNAME. It can also answer on other names, such as a domain you are leaving or a www. variant. The reverse proxy reads APP_HOST_MATCH as a host matcher. It replaces the default rather than extending it.

  1. List all required hostnames in APP_HOST_MATCH.

  2. Include APP_HOSTNAME in the matcher:

    APP_HOST_MATCH=Host(`hephaestus.example.com`) || Host(`old.example.com`)

    Traefik takes one hostname per Host(), joined with ||. Host(`a`,`b`) is rejected and the router does not load at all.

  3. Before deployment, point each name at this host in DNS.

The certificate covers exactly the names in the matcher. Certificate issuance requires a challenge response on each name. A name that does not yet resolve here delays the whole certificate. The first name becomes the certificate's subject. The other names become its alternative names.

APP_HOSTNAME remains the instance's single origin. The SPA, API, and auth issuer all use it. The proxy redirects a browser that opens any other name to APP_HOSTNAME. The app therefore loads from only one host. OAuth callback URLs stay on APP_HOSTNAME.

The API and webhook endpoints deliberately answer on all names in the matcher. An open tab and a provider that still posts to the old name continue to work through the move. The proxy redirects only what a browser loads.

The login callback uses the address where the request arrived. A sign-in started at /api/login/... on another name asks the provider to return to that name. The provider refuses because its only registered callback uses APP_HOSTNAME. Normal access avoids this problem: the proxy redirects the browser before sign-in starts.

  1. Register callbacks for APP_HOSTNAME only.

A callback for another name would permit a session outside the canonical origin.

The proxy controls both the matcher and the redirect. A proxy that does not read this stack's routing configuration can send requests for any names. In that setup, APP_HOST_MATCH has no effect.

  1. Configure the extra names on that proxy.
  2. Configure their certificate there.
  3. Configure its redirect to APP_HOSTNAME.

3. First boot​

docker compose --env-file .env --env-file release-lock.env up -d
docker compose --env-file .env --env-file release-lock.env logs -f database-migration application-server

First startup pulls about 2 GB of images, and the one-shot database-migration service runs all database migrations. The application server starts after it completes. A failed migration shows in its log. It can take a few minutes.

Continue with First steps § 3. Sign in.

Upgrades​

Upgrade owns the upgrade procedure, failed-migration recovery, rollback, update checks, and the release history. Troubleshooting lists the common failures of an installation and their fixes.