Self-hosted Hexclave for a Dokploy Compose service. The stack runs the published stackauth/server image, Postgres 16, ClickHouse 25.10, and one cron loop. Dokploy's Traefik terminates HTTPS.
| Service | Role | Port |
|---|---|---|
hexclave |
Dashboard and API | 8101 dashboard, 8102 API |
postgres |
Application data | internal only |
clickhouse |
Analytics | internal only |
cron |
Email queue and external database sync | internal only |
ClickHouse wants several gigabytes of RAM. Give the Dokploy server enough memory for ClickHouse plus the Hexclave image (about 900 MB compressed).
Use hex or base64url values. Characters such as @, :, and / break the Postgres URL.
openssl rand -base64 32 | tr '+/' '-_' | tr -d '='
openssl rand -hex 32
openssl rand -hex 24Use the first command for HEXCLAVE_SERVER_SECRET, the second for CRON_SECRET, and the third for POSTGRES_PASSWORD, CLICKHOUSE_PASSWORD, and HEXCLAVE_CLICKHOUSE_EXTERNAL_PASSWORD.
Keep HEXCLAVE_SERVER_SECRET and the three passwords unchanged after the first successful boot. Hexclave derives the internal project keys from the server secret.
- Create a project, then a Compose service with type Docker Compose.
- Set the provider to your Git host, this repository, and the branch you deploy.
- Set the compose path to
./docker-compose.yml. - Leave Isolated Deployments off so the Postgres and ClickHouse volumes survive redeploys.
- Open the Environment tab and paste every key from
.env.example. Replace the public URLs and fill every blank secret. - Save. Dokploy writes those values to
.envnext to the compose file. The compose file reads that.env; variables in the UI are not injected on their own.
NEXT_PUBLIC_HEXCLAVE_DASHBOARD_URL and NEXT_PUBLIC_HEXCLAVE_API_URL must be the public https:// URLs you will attach below. They are baked into the dashboard when the container starts.
Use HEXCLAVE_* names. The image also accepts the older STACK_* names, and it exits if both spellings are set to different values.
On the compose service, open Domains and add both hosts to the hexclave service. Enable HTTPS with Let's Encrypt. Leave path, internal path, and strip path empty.
| Host | Container port |
|---|---|
| Dashboard URL host | 8101 |
| API URL host | 8102 |
Create DNS records for both hosts pointing at the Dokploy server. Domain changes on a Compose service apply on the next deploy.
The compose file publishes no host ports. Traefik reaches Hexclave on dokploy-network. Postgres, ClickHouse, and cron stay on the private internal network.
Enable Auto Deploy in the service's general tab. On GitHub, Dokploy deploys when you push the configured branch. On GitLab, Bitbucket, or Gitea, add the webhook URL from the deployment logs. The branch in Dokploy must match the branch you push.
Deploy and wait. The first start pulls the image and migrates Postgres and ClickHouse. Startup also copies the application into a runtime directory, which takes several minutes, so the health check allows ten minutes.
HEXCLAVE_RUN_SEED_SCRIPT is false. The published image seeds a development project and then, on every later boot, tries to update that project's environment config. The image rejects that write and the container exits before the API listens. With seed left on, Dokploy reports dependency hexclave failed to start because the cron service waits for a healthy API.
On an empty database, set HEXCLAVE_RUN_SEED_SCRIPT=true for one deploy. Wait until the hexclave logs show Seeding complete! and the service is healthy. Set it back to false and redeploy. The admin user from that seed is the dashboard login.
After the hexclave service is healthy:
curl -fsS "https://auth-api.example.com/health?db=1"Sign in at the dashboard URL with HEXCLAVE_SEED_INTERNAL_PROJECT_USER_EMAIL and HEXCLAVE_SEED_INTERNAL_PROJECT_USER_PASSWORD.
For each application project, point the SDK at this API:
NEXT_PUBLIC_HEXCLAVE_API_URL=https://auth-api.example.com
HEXCLAVE_API_URL=https://auth-api.example.comUse the project id and keys from this dashboard.
Add these in the Dokploy Environment tab and redeploy. Empty values are ignored.
- Email. Set
HEXCLAVE_EMAIL_HOST,HEXCLAVE_EMAIL_PORT,HEXCLAVE_EMAIL_USERNAME,HEXCLAVE_EMAIL_PASSWORD, andHEXCLAVE_EMAIL_SENDER, or configure SMTP in the dashboard after sign-in. SetHEXCLAVE_EMAILABLE_API_KEYto an Emailable key to validate addresses. The defaultdisable_email_validationskips that check. - Webhooks. Set
HEXCLAVE_SVIX_API_KEY. LeaveHEXCLAVE_SVIX_SERVER_URLempty for Svix Cloud. Set it, andNEXT_PUBLIC_HEXCLAVE_SVIX_SERVER_URLwhen the browser URL differs, for a self-hosted Svix. - File storage. Set the
HEXCLAVE_S3_*variables. - AI dashboard features. Set
HEXCLAVE_OPENROUTER_API_KEY. - Custom email sending. Set
HEXCLAVE_FREESTYLE_API_KEYandHEXCLAVE_FREESTYLE_SNAPSHOT_IDonly after that snapshot exists in the same Freestyle account. Bootstrap it from the Hexclave repo withpnpm --filter @hexclave/backend freestyle:bootstrap-snapshotbefore the first boot that needs it.
- Back up the
postgres_dataandclickhouse_datavolumes. - Read the Hexclave release notes and compare the current server env template with your saved environment.
- Pick a newer commit tag from stackauth/server. This stack is pinned to
stackauth/server:4afea67(index digestsha256:8a6ea3449cbf29b88c3601ba63e0cc1a67b39dd211ec32038c542ce7645c0789as of 2026-10-20).latestis a separate, older tag. - Change the image tag in
docker-compose.yml, commit, and push. - Confirm
https://<api-host>/health?db=1, dashboard sign-in, cron logs, and any email or webhook flow you use.
The image runs migrations on startup. This compose file runs a single Hexclave container, so that startup migration is the upgrade migration. Keep the previous tag in git history so you can point the image back and redeploy.
cron calls these API routes every 60 seconds with Authorization: Bearer $CRON_SECRET:
/api/latest/internal/email-queue-step/api/latest/internal/external-db-sync/sequencer/api/latest/internal/external-db-sync/poller
A non-2xx response is printed as a failure in the cron service logs. Run one cron service per environment.