Motivation: Release images should be built, checked, published, and consumed through the same Nostr-native OCI path operators will use, without relying on an unreviewed local release procedure. Approach: Add a checked-in container manifest, a Docker-to-OCI layout helper, automatic tag publication and pull verification, a safe exact-tag backfill workflow, and deployment CI that imports and runs the exact generated layout. Correctness: Release tags come only from reviewed OCI index annotations; ordinary publication preserves prior tags; historical backfills cannot move latest or prerelease channels; generated images and temporary resources use bounded, validated names and cleanup. Excluded scope: This change does not alter ngit-grasp runtime behavior, change package versions, create v3.0.2, publish a container, move a release tag, or run the heavyweight container build in the coding VM. Validation: git diff --check; shellcheck on all container scripts; actionlint on all affected workflows; ngit parsing of .ngit/containers.yaml; canonical source and v3.0.1 tag resolution. The PR pipeline performs the full OCI build, import, and deployment test. Assisted-by: Codex (GPT-5)
5.2 KiB
Deploy with Docker or Podman
This is the portable default for a Linux host. The repository ships a
multi-stage Dockerfile, a loopback-only Compose service, and an optional
Caddy overlay for automatic HTTPS.
The image contains ngit-grasp, Git, CA certificates, and a small init process.
It prepares /data and then runs ngit-grasp as UID/GID 10001.
Stable release images are published through Nostr, with their OCI blobs stored on Blossom. Docker and Podman can pull them through the ncontainer gateway:
docker pull ncontainer.io/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp:latest
latest tracks the newest stable release. Replace it with an explicit release
version for a reproducible deployment. Release candidates are also published
under their prerelease channel, such as rc.
Prerequisites
- Docker Engine with Compose v2, or a compatible Podman Compose setup
- durable local storage for the named volume
- a domain whose DNS points to the host
- ports 80 and 443 available when using bundled Caddy
Review the deployment contract before placing the state volume on remote or managed storage.
Fresh VPS with automatic HTTPS
Create the small deployment environment file:
cp deploy.env.example .env
Set NGIT_DOMAIN in .env, then start the relay and Caddy:
export NGIT_IMAGE="ncontainer.io/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp:latest"
docker compose -f compose.yaml -f compose.caddy.yaml pull ngit-grasp
docker compose -f compose.yaml -f compose.caddy.yaml up --no-build -d
docker compose -f compose.yaml -f compose.caddy.yaml ps
scripts/verify-deployment.sh https://ngit.example.com
Caddy obtains and renews the certificate. The relay is also published on
127.0.0.1:7334 for local diagnostics, but it is not directly reachable from
the network.
The bundled Caddy configuration serves ngit-grasp at the domain root. To share
a hostname using NGIT_BASE_PATH, use an existing reverse proxy and configure
its path routing explicitly.
Existing reverse proxy
Start only the relay service:
export NGIT_IMAGE="ncontainer.io/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp:latest"
docker compose pull ngit-grasp
docker compose up --no-build -d
curl -H 'Accept: application/nostr+json' http://127.0.0.1:7334
To build a checked-out source revision instead, leave NGIT_IMAGE unset and
use docker compose up --build -d with the same Compose file selection.
Proxy the public HTTPS hostname to http://127.0.0.1:7334. Preserve WebSocket
upgrades, request methods, bodies, and query strings. Forwarded client IP
headers are ignored by default; configure NGIT_TRUSTED_PROXY_CIDRS only after
identifying the exact container-visible proxy address and keeping the backend
private.
State and identity
The ngit-grasp-data named volume is mounted at /data and contains all
durable state. The first start creates /data/.relay-owner.nsec; retaining the
volume retains the public identity advertised in NIP-11.
Inspect the resolved volume without guessing its Compose prefix:
docker volume inspect ngit-grasp_ngit-grasp-data
Do not use docker compose down --volumes in production. It deletes the relay
identity, events, and Git repositories.
Operations
# Logs
docker compose logs --follow ngit-grasp
# Controlled restart
docker compose restart ngit-grasp
# Stop while retaining state
docker compose stop
# Start again
docker compose start
When the Caddy overlay is active, include both -f arguments for commands that
must operate on the complete project.
Backup
Stop the writer, archive the complete volume, and start it again:
docker compose stop ngit-grasp
docker run --rm \
--volume ngit-grasp_ngit-grasp-data:/data:ro \
--volume "$PWD:/backup" \
alpine:3.22 \
tar -czf /backup/ngit-grasp-data.tar.gz -C /data .
docker compose start ngit-grasp
Store the archive away from the Docker host. Test restoration into a separate, non-public deployment before relying on it.
Upgrade
Read CHANGELOG.md, take a backup, select an explicit release tag, and replace
the container without overlapping the old and new writers:
export NGIT_IMAGE="ncontainer.io/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp:3.0.2"
docker compose pull ngit-grasp
docker compose stop ngit-grasp
docker compose up --no-build -d ngit-grasp
scripts/verify-deployment.sh https://ngit.example.com
Replace 3.0.2 with the intended release. For a source-built deployment,
check out that tag and run docker compose build --pull ngit-grasp before
starting the service instead.
For storage-changing releases, follow the linked migration guide and restore the pre-upgrade volume snapshot before attempting a binary rollback.
Validate the image locally
The repository includes a destructive-to-test-resources-only integration check. It builds the image, creates uniquely named temporary container and volume resources, replaces the container, and confirms the NIP-11 pubkey did not change:
scripts/test-container-deployment.sh
Set CONTAINER_ENGINE=podman to exercise a compatible Podman CLI.