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)
6.2 KiB
Deploy on managed hosting
The supplied container can run on Railway, Render, and Fly.io when each service
has a custom domain, one persistent /data volume, and exactly one running
instance. These platforms terminate TLS and proxy WebSockets to ngit-grasp.
Managed hosting is best for a small or moderate relay whose operator accepts brief upgrade downtime. For a large existing relay, use NixOS, systemd, or Compose so startup migrations and storage snapshots remain under direct operator control.
Read the deployment contract first. In particular, provider replicas do not make ngit-grasp highly available: their local volumes do not replicate application state.
Common requirements
For every provider:
- set
NGIT_DOMAINto the final custom hostname, not the provider hostname; - mount durable storage at
/databefore the first successful start; - keep the instance count at one and disable scale-to-zero;
- keep
NGIT_BASE_PATH=/unless path routing has been designed explicitly; - allow at least five minutes between
SIGTERMand forced termination; and - take an external backup of
/data, including.relay-owner.nsec.
The image maps a platform-provided PORT to 0.0.0.0:${PORT}. Do not set
NGIT_BIND_ADDRESS unless the provider template below does so explicitly.
Railway
railway.toml selects the Dockerfile, uses / as the health check, disables
old/new deployment overlap, and gives shutdown five minutes. Railway storage is
configured separately from config-as-code. See Railway's
config-as-code reference
and volume guide for the provider-side
details.
From the repository root with the Railway CLI installed:
railway login
railway init
railway add --service ngit-grasp
railway service ngit-grasp
railway variable set NGIT_DOMAIN=ngit.example.com NGIT_BASE_PATH=/
railway volume add --mount-path /data
railway up
railway domain ngit.example.com
Add the DNS records returned by railway domain, then inspect status and verify
the public relay:
railway domain status ngit.example.com
railway deployment list --json
scripts/verify-deployment.sh https://ngit.example.com
Do not add replicas or multi-region configuration. Railway mounts a new volume as root; the image entrypoint prepares it and drops privileges before starting ngit-grasp.
Railway volume backups are useful recovery points, but also export or snapshot state outside the platform. During a large storage migration, increase the health-check timeout or move the upgrade to a directly operated host.
Render
render.yaml describes a paid starter web service with a 10 GB disk mounted
at /data, a root health check, a five-minute shutdown delay, and automatic
deploys disabled. Render prompts for NGIT_DOMAIN when the Blueprint is
created. The fields follow Render's
Blueprint specification; its
persistent disk guide describes the storage
and scaling constraints.
The published image is available to provider workflows that accept a public Docker-compatible registry reference:
ncontainer.io/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp:latest
Pin an explicit release version instead of latest for repeatable production
deployments. The supplied render.yaml remains source-backed because Render
Blueprints require a repository connected through one of Render's supported
Git providers. Use Render's Public Git Repository flow with the canonical
URL:
https://gitnostr.com/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp.git
Choose Docker and apply the values from render.yaml in the service form:
- environment variable
NGIT_DOMAIN=ngit.example.com; - persistent disk mounted at
/data; - health check path
/; - maximum shutdown delay 300 seconds; and
- one instance with automatic deploys disabled.
Add and verify the custom domain before publishing it in repository announcements. Render services backed by a persistent disk cannot run multiple instances and have brief downtime during deploys, which matches the relay's single-writer requirement.
After a manual deploy, run:
scripts/verify-deployment.sh https://ngit.example.com
Use Render disk snapshots plus an independent backup. Only /data persists;
all other container filesystem changes are ephemeral.
Fly.io
Fly volumes are local to one Machine and are not automatically replicated. The template therefore disables autostop, uses an immediate replacement strategy, and must be deployed with high-availability seeding disabled. See Fly's configuration reference and volume overview for the underlying platform behavior.
Copy and edit the template:
cp fly.toml.example fly.toml
# Set a unique app name, primary region, and NGIT_DOMAIN in fly.toml.
fly config validate --strict --config fly.toml
Create the app and one volume in the same region, then deploy one Machine:
fly apps create replace-with-a-unique-ngit-grasp-name
fly volumes create ngit_grasp_data --region ord --size 10
fly deploy --ha=false
fly scale count 1
fly certs add ngit.example.com
Use the app name and region selected in fly.toml, add the DNS records shown by
fly certs, then verify:
fly checks list
fly scale show
scripts/verify-deployment.sh https://ngit.example.com
Never scale above one: Fly creates a separate empty volume for each additional Machine and does not copy ngit-grasp state. A single Machine and volume can be unavailable after host failure, so maintain an external backup rather than relying only on Fly snapshots.
Upgrades
Managed volume deployments cannot provide a safe zero-downtime writer handoff. Expect brief downtime:
- disable automatic deploys;
- take a complete provider snapshot and an external backup;
- deploy the pinned revision with one instance;
- inspect startup and integrity logs; and
- run the public verifier.
For a storage-changing release, restore both the prior executable and its
matching pre-upgrade /data snapshot when rolling back.