Hosted onhoraciohdev.dev.hyper.mediavia theHypermedia Protocol

Seed Node Deployment

Self-hosted deployment system for Seed nodes. A single script handles
first-time setup, configuration, container orchestration, backups,
and automatic updates.

Quick Start

curl -fsSL https://deploy.seedhypermedia.com | sh

The bootstrap script installs Docker and Bun (if missing), downloads the
deployment engine, and launches an interactive wizard to configure your
node. After setup you manage everything through the seed-deploy CLI.

Architecture

deploy.sh Minimal bootstrap — installs Docker + Bun, downloads deploy.js | v deploy.ts Main deployment engine (bundled to dist/deploy.js) | - Interactive wizard (first run) | - Headless deploy (subsequent runs / cron) | - Full CLI for node management v docker-compose.yml Container definitions for proxy, web, and daemon

Files

| File | Purpose | | -------------------- | ---------------------------------------- | | `deploy.sh` | One-line bootstrap installer | | `deploy.ts` | Deployment engine source (TypeScript) | | `deploy.test.ts` | Test suite (115 tests, 257 assertions) | | `dist/deploy.js` | Committed production bundle (Bun target) | | `docker-compose.yml` | Docker Compose service definitions |

Modes of Operation

1. Interactive Wizard (first run)

When no config.json exists, the script launches a terminal wizard:


    Public hostname — the https:// URL for the node (required)

    Environment — Production / Staging / Development

    Log level — Debug / Info / Warn / Error

    Gateway mode — whether the node serves all known public content

    Analytics — enable Plausible.io traffic dashboard

    Contact email — optional, for security update notifications

The wizard also detects legacy installations (from website_deployment.sh)
and offers a migration path, pre-filling values from the old config.

2. Headless Deploy (subsequent runs)

When config.json exists, the script runs without prompts:


    Self-updates deploy.js from the upstream repo (cron only)

    Fetches docker-compose.yml and compares SHA-256 with the stored hash

    If nothing changed and all containers are healthy, skips redeployment

    Otherwise: pulls images first (while old containers serve traffic),

then recreates containers from cache — minimizes downtime


    Prunes unused Docker images after successful deploy

3. Reconfiguration

seed-deploy deploy --reconfigure

Re-runs the wizard with current values shown as placeholders. Press Tab
to keep a value, type to change it, or press Enter to clear optional
fields. Changed fields are marked with a pencil icon in the summary.

CLI Reference

seed-deploy [command] [options]
| Command | Description | | ---------------- | ------------------------------------------ | | `deploy` | Deploy or update node (default command) | | `stop` | Stop and remove all containers | | `start` | Start containers without re-deploying | | `restart` | Restart all containers | | `status` | Health, versions, connectivity, disk, cron | | `config` | Print current config (secrets redacted) | | `logs [service]` | Tail logs (daemon, web, or proxy) | | `cron [remove]` | Install or remove auto-update cron jobs | | `backup [path]` | Create portable backup of node data | | `restore <file>` | Restore node data from backup archive | | `uninstall` | Remove all containers, data, and config |
| Option | Description | | ----------------- | ----------------------------------------------- | | `--reconfigure` | Re-run the setup wizard to change configuration | | `-h`, `--help` | Show help message | | `-v`, `--version` | Show script version |

Environment Presets

A single "Environment" choice controls multiple settings:

| Environment | Tag | Network | Use Case | | ----------- | -------- | ------- | ------------------------------ | | Production | `latest` | Mainnet | Stable releases (recommended) | | Staging | `dev` | Mainnet | Dev builds on real network | | Development | `dev` | Testnet | Dev builds on isolated network |

Configuration

Stored at <seed-dir>/config.json. User-facing fields:

| Field | Required | Description | | ------------- | -------- | ---------------------------------------- | | `domain` | Yes | Public hostname including `https://` | | `email` | No | Contact email for security notifications | | `environment` | Yes | `prod`, `staging`, or `dev` | | `gateway` | Yes | Serve all known public content | | `analytics` | Yes | Enable Plausible.io web analytics |

Internal fields (managed by the script): compose_url, compose_sha,
compose_envs, release_channel, testnet, link_secret,
last_script_run.

Docker Services

Three core containers, plus optional metrics:

| Container | Image | Ports | Purpose | | ----------- | ------------------- | ---------------- | ------------------- | | seed-proxy | caddy:2 | 80, 443, 443/udp | Reverse proxy + TLS | | seed-web | seedhypermedia/web | 3000 | Web frontend | | seed-daemon | seedhypermedia/site | 56000, 56000/udp | P2P daemon + API | | prometheus | prom/prometheus | — | Metrics collection | | grafana | grafana/grafana | — | Metrics dashboards |

All containers run as the host user (SEED_UID:SEED_GID) — no root inside
containers. All bind mounts use the :z flag for SELinux compatibility.

Automatic Updates

The cron system installs two jobs:

| Schedule | Task | | ----------- | ---------------------------------------------- | | 02:00 daily | Run deploy.js — pull images, update containers | | Every 4 hrs | Prune unused Docker images older than 1 hour |

Install with seed-deploy cron, remove with seed-deploy cron remove.

Backup & Restore

Backup creates a .tar.gz containing config.json, docker-compose.yml,
and the web/, daemon/, and proxy/ data directories. Containers are
stopped during backup for data consistency and restarted after.

seed-deploy backup # default: <seed-dir>/backups/ seed-deploy backup /tmp/my-backup.tgz # custom path

Restore extracts a backup archive, optionally lets you edit the
configuration via the wizard, restores cron jobs, and runs a full deploy.

seed-deploy restore /path/to/backup.tar.gz

Edge Cases Handled


    glibc < 2.25deploy.sh checks glibc version before attempting

Bun install. Prints supported OS versions and exits cleanly.


    SELinux — all Docker bind mounts use :z flag. Without it,

Fedora/CentOS/RHEL silently block container access to host files.


    Legacy installations — detects old website_deployment.sh containers

(docker run-based). Stops and removes them before first
docker compose up to avoid name conflicts.


    Non-root operation — containers run as the host user. Caddy binds

ports 80/443 via CAP_NET_BIND_SERVICE file capability. sudo is
only used when creating directories outside the user's home.


    Disk exhaustion — old Docker images are pruned both inline after

deploys and on a 4-hour cron schedule.


    No-change deploys — skipped entirely when compose SHA matches and

all containers are healthy. Shows a hint for --reconfigure.


    Self-update — in headless mode, the script fetches its own latest

version before deploying. Takes effect on the next run.


    rsync non-fatal — the daemon's monitoring config export uses

rsync -rlt (no owner/group) and wraps failures as warnings to avoid
blocking the daemon start.

Environment Variables

For testing and development:

| Variable | Purpose | | ----------------- | ------------------------------------------- | | `SEED_DIR` | Seed directory (default: deploy.js dirname) | | `SEED_DEPLOY_URL` | Base URL for fetching compose + deploy.js | | `SEED_BRANCH` | Git branch for deploy.sh (default: main) |

Example local testing setup:

# Terminal 1: serve files locally cd ops && python3 -m http.server 9999 # Terminal 2: run deploy against local server SEED_DIR=/tmp/seed-test SEED_DEPLOY_URL=http://localhost:9999 sh ops/deploy.sh

Development

cd ops bun install # install dependencies bun test # run test suite bun run build # bundle to dist/deploy.js

The dist/deploy.js bundle is committed to the repo. A CI workflow
verifies the bundle matches the source on every push to ops/.

Do you like what you are reading? Subscribe to receive updates.

Unsubscribe anytime