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
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:pg16getmeili/meilisearch:v1.12nginx: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.