Skip to main content

Air-gapped and private registries

The normal deployment pulls public images from Docker Hub. For a private registry or a fully air-gapped host, keep TOW_VERSION as the release selector and override every TOW image repository together.

Point the stack at your registry

Copy the shipped example to the ignored override file and replace the registry namespace:

cp compose.override.example.yaml compose.override.yaml
compose.override.yaml
services:
schema-migrate:
image: registry.example.com/tow/backend:${TOW_VERSION:?Set TOW_VERSION in .env}
backend:
image: registry.example.com/tow/backend:${TOW_VERSION:?Set TOW_VERSION in .env}
search-worker:
image: registry.example.com/tow/backend:${TOW_VERSION:?Set TOW_VERSION in .env}
email-worker:
image: registry.example.com/tow/backend:${TOW_VERSION:?Set TOW_VERSION in .env}
inbound-email-worker:
image: registry.example.com/tow/backend:${TOW_VERSION:?Set TOW_VERSION in .env}
migration-worker:
image: registry.example.com/tow/backend:${TOW_VERSION:?Set TOW_VERSION in .env}
frontend:
image: registry.example.com/tow/frontend:${TOW_VERSION:?Set TOW_VERSION in .env}
docs:
image: registry.example.com/tow/docs:${TOW_VERSION:?Set TOW_VERSION in .env}
authentik-bootstrap:
image: registry.example.com/tow/backend:${TOW_VERSION:?Set TOW_VERSION in .env}

Compose loads compose.override.yaml automatically next to compose.yaml, and it is operator-owned, so updates never touch it. If you also carry other override entries (published ports, volume mappings), keep them in the same file.

Mirror the images

Mirror the TOW images for your pinned release:

  • anistow/tow-backend:<TOW_VERSION>
  • anistow/tow-frontend:<TOW_VERSION>
  • anistow/tow-docs:<TOW_VERSION> (only with the docs profile)

And the pinned third-party images from compose.yaml:

  • pgvector/pgvector:pg16
  • getmeili/meilisearch:v1.12
  • nginx:1.27-alpine
  • With the authentik profile: ghcr.io/goauthentik/server:<AUTHENTIK_TAG>, postgres:16-alpine, redis:7-alpine

For a fully air-gapped host, pre-load them with docker save / docker load or a registry mirror inside the enclave.

Operations scripts without a kit clone

Air-gapped hosts that cannot clone the deploy kit can extract the version-matched operations scripts from the backend image they already have:

ops_image="$(docker compose images -q backend)"
ops_container="$(docker create "$ops_image")"
docker cp "${ops_container}:/usr/local/share/tow-ops/." scripts/
docker rm "$ops_container" >/dev/null
chmod 750 scripts/*.sh

Everything else, including installation questions, upgrades, and backups, works exactly as on a connected host once the images are reachable.