Bondery Docs

API container (GHCR + external Redis)

Bondery production / advanced operators — self-hosters use the unified deploy/bondery stack (webapp + API + Redis + Postgres). See Installation.

CI builds and pushes the API image to GHCR.

Current package: ghcr.io/usebondery/api

Deployment options

  • Recommended: deploy/bondery/docker-compose.yml — webapp + API + bundled Redis.

  • Advanced: API image alone + BONDERY_PRIVATE_REDIS_URL pointing to Redis you already operate.

  • Bundled Redis default: BONDERY_PRIVATE_REDIS_URL=redis://redis:6379 (compose service hostname).

  • External Redis: set BONDERY_PRIVATE_REDIS_URL to your Redis; do not start the compose Redis service.

  • Empty BONDERY_PRIVATE_REDIS_URL is invalid in production.

Local laptop Redis: deploy/bondery/docker-compose.dev-redis.yml (pnpm run start:redis, port 26636) — not the production deploy/bondery stack.

Tagging model

Three layers — do not confuse them:

LayerExampleRole
Git tagv1.7.1Triggers unified release.yml
Semver image tag1.7.1Immutable artifact — pin production via BONDERY_INFRA_VERSION (api + webapp)
Channel image tagproduction, beta, sha-abc1234Floating or traceability tags

Published Docker tags

ChannelCI triggerTags pushedUse
IntegrationPush to main (API-related paths)beta, sha-<short-sha>Optional staging; rollback/debug on main
ProductionGit tag vX.Y.Z on releaseX.Y.Z, productionLive api + webapp (unified release)

Compose defaults: omit BONDERY_INFRA_VERSION to pull the floating production channel. Set BONDERY_INFRA_VERSION=X.Y.Z to pin both api and webapp — see deploy/bondery/.env.example.

No minor-line tags (e.g. 1.7) — only full X.Y.Z semver.

Channel tag naming (beta vs alternatives)

TagWho uses itFits Bondery?
betaMobile/games, pre-release channelsOK if you run beta.api.*
stagingMost SaaSBest if env is called “staging”
previewVercel-style per-change previewsBetter for per-PR images
canaryPartial traffic rolloutsDifferent pattern
latestDocker Hub defaultsAvoid
main / edgeGitOpsFine if main == always deployable

Today: workflow uses beta for the floating main build and production for the floating release build.

GHCR package UI

OS tab: linux/amd64 and unknown/unknown

OSWhat it is
linux/amd64The runnable container image
unknown/unknownBuildKit attestation metadata (not runnable)

Workflows set provenance: false and sbom: false so future pushes only publish the runnable image.

Platform

Images are linux/amd64 only (Hetzner CX32).

GitHub setup (one-time)

Secrets

SecretRequiredPurpose
BONDERY_OPS_GHCR_WRITE_TOKENYesPush/pull images on GHCR — loaded from Infisical production in CI (ghcr-login-infisical)
BONDERY_OPS_TURBO_TEAMOptional (GitHub Actions variable)Turbo remote cache team
BONDERY_OPS_TURBO_TOKENOptional (secret)Turbo remote cache

GHCR package visibility

Packages are public so self-hosters can pull without GitHub credentials.

release branch

Production git tags must point at commits on release.

Release process

  1. Merge to main → integration image (:beta, :sha-*).
  2. Promote + tag:
git checkout release
git merge origin/main
git push origin release
git tag v1.7.1
git push origin v1.7.1
  1. CI publishes ghcr.io/usebondery/api:1.7.1 and ghcr.io/usebondery/webapp:1.7.1, promotes :production after smoke.
  2. Set BONDERY_INFRA_VERSION=1.7.1 in Dokploy and redeploy api + webapp together.

See the bondery-release skill (dokploy-pins reference) for the full stack pin workflow.

Unified Compose

docker network create dokploy-network   # once
cd deploy/bondery
cp .env.example .env
# Fill secrets; set BONDERY_INFRA_VERSION to semver or omit for :production
docker compose up -d api webapp

Attach Traefik only via committed labels (BONDERY_INFRA_API_DOMAIN / BONDERY_INFRA_WEBAPP_DOMAIN). Never expose Redis.

Full Dokploy runbook: dokploy.md.

API image + external Redis

Use when Redis is already managed outside this stack.

Dokploy — Docker Image fields

Use the full image reference. If you enter only usebondery/api:production, Docker pulls from Docker Hub, not GHCR.

FieldValue
Docker imageghcr.io/usebondery/api:1.7.0
Registry URLleave empty, or ghcr.io if required
Username / Passwordempty for public packages
Container port26631
Domainyour API hostname

Set BONDERY_PRIVATE_REDIS_URL to your Redis URL (rediss:// when TLS is required). Do not leave it empty.

Verify on the host

docker pull ghcr.io/usebondery/api:1.7.0

Rollback

EnvironmentAction
Production (Compose)Restore previous BONDERY_INFRA_VERSION and redeploy api + webapp together
Staging / mainRedeploy :sha-<commit>

Local smoke test

cd deploy/bondery && cp .env.example .env
# add secrets, then:
docker network create dokploy-network || true
cp docker-compose.override.yml.example docker-compose.override.yml
docker compose up -d

# Or build the API image locally (external Redis style)
docker build -f apps/api/Dockerfile -t api:local .
docker run --rm -p 26631:26631 --env-file apps/api/.env.production.local api:local

Boot validates required env via assertRequiredEnvAtStartup (same list as pnpm run check:env -w api) before Fastify listens.

On this page