Private Registry Deployment¶
Audience: Administrators deploying Sentari using an internal container registry (Nexus, Harbor, or Artifactory)
This guide covers two scenarios:
- Online mirroring — Your registry proxies or mirrors ghcr.io. Servers have outbound internet access (or access to your registry which does).
- Air-gap / offline delivery — No outbound internet access at all. Images are transferred via physical media or a one-way file transfer system.
Image inventory¶
The following images must be available in your registry for a complete Sentari deployment:
| Image | Source | Purpose |
|---|---|---|
ghcr.io/sentari-dev/sentari/server:<version> |
GitHub Container Registry | API server, Celery worker, Beat scheduler |
ghcr.io/sentari-dev/sentari/docs:<version> |
GitHub Container Registry | API documentation site |
timescale/timescaledb:2.14.2-pg16 |
Docker Hub | Database (recommended) |
postgres:16-alpine |
Docker Hub | Database (plain PostgreSQL alternative) |
redis:7.2-alpine |
Docker Hub | Task queue broker |
Replace <version> with the Sentari release tag (e.g., 1.0.0). Sentari images
use the linux/amd64 architecture.
Option 1: Online mirroring via Nexus Docker proxy¶
This approach lets Nexus act as a pull-through cache. The first pull fetches from ghcr.io; subsequent pulls are served from the Nexus cache without hitting the internet.
1.1 Configure a Nexus Docker proxy repository for ghcr.io¶
In the Nexus Repository Manager UI:
- Navigate to Administration > Repositories > Create Repository.
- Select recipe: docker (proxy).
- Configure the repository:
| Field | Value |
|---|---|
| Name | ghcr-proxy |
| HTTP port | 8082 (or leave blank to use the group port) |
| Remote storage URL | https://ghcr.io |
| Docker Index | Use Docker Hub (leave default) |
| Allow anonymous docker pull | Disabled (recommended) |
| Authentication | See step 1.2 |
- Repeat for Docker Hub if you are also proxying
timescale/timescaledbandredis:
| Name | Remote storage URL |
|---|---|
dockerhub-proxy |
https://registry-1.docker.io |
- Add both proxy repositories to a docker (group) repository (e.g.,
docker-group) so clients use a single endpoint.
1.2 Configure ghcr.io credentials in Nexus¶
ghcr.io requires authentication for private repositories:
- Generate a GitHub personal access token (PAT) with the
read:packagesscope. - In Nexus, go to Administration > Security > Anonymous Access and ensure anonymous access is disabled on the group repository.
- On the
ghcr-proxyrepository, set: - Authentication type: Username
- Username: your GitHub username (or a service account)
- Password: the PAT generated above
Nexus uses these credentials when fetching from ghcr.io on behalf of your clients.
1.3 Pull Sentari images through the proxy¶
Clients authenticate to Nexus (not to ghcr.io directly):
docker login nexus.internal:8443
docker pull nexus.internal:8443/sentari/sentari/server:1.0.0
docker pull nexus.internal:8443/sentari/sentari/docs:1.0.0
docker pull nexus.internal:8443/timescale/timescaledb:2.14.2-pg16
docker pull nexus.internal:8443/redis:7.2-alpine
Option 2: Air-gap transfer¶
Use this approach when no network path exists between the transfer workstation and the target environment.
2.1 Recommended: skopeo copy¶
skopeo is the preferred tool because it copies multi-architecture manifests directly
between registries without requiring a local Docker daemon and without repackaging images.
Install skopeo on the transfer workstation (requires network access to ghcr.io):
# RHEL / CentOS / Fedora
dnf install skopeo
# Debian / Ubuntu
apt install skopeo
Copy each image directly into your internal registry:
# Sentari server
skopeo copy \
docker://ghcr.io/sentari-dev/sentari/server:1.0.0 \
docker://nexus.internal:8443/sentari/sentari/server:1.0.0
# Sentari docs
skopeo copy \
docker://ghcr.io/sentari-dev/sentari/docs:1.0.0 \
docker://nexus.internal:8443/sentari/sentari/docs:1.0.0
# TimescaleDB
skopeo copy \
docker://docker.io/timescale/timescaledb:2.14.2-pg16 \
docker://nexus.internal:8443/timescale/timescaledb:2.14.2-pg16
# Redis
skopeo copy \
docker://docker.io/redis:7.2-alpine \
docker://nexus.internal:8443/redis:7.2-alpine
If the source registry requires authentication:
skopeo copy \
--src-creds=<github-username>:<pat-token> \
docker://ghcr.io/sentari-dev/sentari/server:1.0.0 \
--dest-creds=<nexus-user>:<nexus-password> \
docker://nexus.internal:8443/sentari/sentari/server:1.0.0
To copy to a directory for physical media transfer instead:
skopeo copy \
docker://ghcr.io/sentari-dev/sentari/server:1.0.0 \
dir:/mnt/usb/sentari-images/server-1.0.0
# On the air-gapped side:
skopeo copy \
dir:/mnt/usb/sentari-images/server-1.0.0 \
docker://nexus.internal:8443/sentari/sentari/server:1.0.0
2.2 Alternative: docker save / load¶
Use docker save and docker load when skopeo is not available. Note that this
approach produces single-architecture archives.
On the transfer workstation (with internet access):
# Pull all images
docker pull ghcr.io/sentari-dev/sentari/server:1.0.0
docker pull ghcr.io/sentari-dev/sentari/docs:1.0.0
docker pull timescale/timescaledb:2.14.2-pg16
docker pull redis:7.2-alpine
# Export to a tarball
docker save \
ghcr.io/sentari-dev/sentari/server:1.0.0 \
ghcr.io/sentari-dev/sentari/docs:1.0.0 \
timescale/timescaledb:2.14.2-pg16 \
redis:7.2-alpine \
| gzip > sentari-1.0.0-images.tar.gz
# Verify the archive
sha256sum sentari-1.0.0-images.tar.gz > sentari-1.0.0-images.tar.gz.sha256
Transfer sentari-1.0.0-images.tar.gz and the .sha256 file to the air-gapped
environment via approved transfer procedure.
On the air-gapped server:
# Verify integrity
sha256sum -c sentari-1.0.0-images.tar.gz.sha256
# Load all images
docker load < sentari-1.0.0-images.tar.gz
# Retag for your internal registry
docker tag ghcr.io/sentari-dev/sentari/server:1.0.0 nexus.internal:8443/sentari/sentari/server:1.0.0
docker tag ghcr.io/sentari-dev/sentari/docs:1.0.0 nexus.internal:8443/sentari/sentari/docs:1.0.0
docker tag timescale/timescaledb:2.14.2-pg16 nexus.internal:8443/timescale/timescaledb:2.14.2-pg16
docker tag redis:7.2-alpine nexus.internal:8443/redis:7.2-alpine
# Push to the internal registry
docker push nexus.internal:8443/sentari/sentari/server:1.0.0
docker push nexus.internal:8443/sentari/sentari/docs:1.0.0
docker push nexus.internal:8443/timescale/timescaledb:2.14.2-pg16
docker push nexus.internal:8443/redis:7.2-alpine
Configuring Sentari to use your registry¶
Docker Compose¶
Set the REGISTRY variable in your .env file:
REGISTRY=nexus.internal:8443/sentari
VERSION=1.0.0
The docker-compose.yml file references ${REGISTRY:-ghcr.io/sentari-dev/sentari}
for Sentari images. Database and Redis images are declared with their own image:
lines — override them directly in docker-compose.yml if you are mirroring Docker
Hub images:
db:
image: nexus.internal:8443/timescale/timescaledb:2.14.2-pg16
redis:
image: nexus.internal:8443/redis:7.2-alpine
If your Nexus registry requires authentication, log in on the deployment host before running Compose:
docker login nexus.internal:8443
Helm (Kubernetes / OpenShift)¶
The chart does not ship registry or secrets values files — author your own overrides
file (named whatever you like, e.g. my-registry.yaml) or pass the values via --set.
Override the registry at install time:
helm install sentari deploy/helm/sentari/ \
--namespace sentari \
--set image.registry=nexus.internal:8443 \
--set database.image.registry=nexus.internal:8443 \
--set redis.image.registry=nexus.internal:8443 \
--set secrets.existingSecretName=sentari-credentials
Or place the overrides in your own values file:
# my-registry.yaml
image:
registry: nexus.internal:8443
repository: sentari-dev/sentari/server
tag: "1.0.0"
database:
image:
registry: nexus.internal:8443
redis:
image:
registry: nexus.internal:8443
Apply (supply secrets via --set, in the same file, or with a pre-created Secret
via secrets.existingSecretName):
helm install sentari deploy/helm/sentari/ \
--namespace sentari \
-f my-registry.yaml \
--set secrets.existingSecretName=sentari-credentials
imagePullSecrets (Kubernetes / OpenShift)¶
If your Nexus registry requires authentication, create a pull secret and reference it in the Helm values:
kubectl create secret docker-registry sentari-pull \
--namespace sentari \
--docker-server=nexus.internal:8443 \
--docker-username=<nexus-user> \
--docker-password=<nexus-password>
Reference the secret in your overrides file:
imagePullSecrets:
- name: sentari-pull
The Helm chart propagates imagePullSecrets to all workloads (API, worker, Beat).
Image verification¶
Sentari release images are signed with Cosign. Verify signatures before loading images into your registry.
Cosign signature verification¶
Sentari images are signed keyless (Sigstore) by the GitHub Actions release
workflow. The OIDC issuer is https://token.actions.githubusercontent.com, and the
certificate identity is the release workflow's identity under the sentari-dev/sentari
repository. The example below is illustrative; substitute the exact workflow identity
for the release you are verifying (obtain it from the release notes or by inspecting
the signature with cosign verify in attestation mode):
# Install cosign (https://docs.sigstore.dev/cosign/installation/)
# Replace the identity regexp with the actual release-workflow OIDC identity.
cosign verify \
--certificate-identity-regexp="^https://github.com/sentari-dev/sentari/\.github/workflows/release\.yml@.*" \
--certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
ghcr.io/sentari-dev/sentari/server:1.0.0
A valid signature produces output similar to:
Verification for ghcr.io/sentari-dev/sentari/server:1.0.0 --
The following checks were performed on each of these signatures:
- The cosign claims were validated
- Existence of the claims in the transparency log was verified offline
- The code-signing certificate claims were validated
Contact your Sentari representative for the release signing key if you require key-pinned verification.
SHA256 digest verification¶
Release digests are published in the Sentari release notes and at
https://github.com/sentari-dev/sentari/releases. Verify before transfer:
# Get the digest from ghcr.io
docker inspect --format='{{index .RepoDigests 0}}' ghcr.io/sentari-dev/sentari/server:1.0.0
# Or with skopeo (no pull required)
skopeo inspect docker://ghcr.io/sentari-dev/sentari/server:1.0.0 \
| python3 -c "import sys,json; d=json.load(sys.stdin); print(d['Digest'])"
Compare the output against the digest published in the release notes.
For Harbor and Artifactory, the workflow is identical to Nexus. The proxy repository
configuration UI differs, but the skopeo copy and Helm override steps are unchanged.
For additional assistance, contact your Sentari representative or visit the support portal.