A Seed site is a Seed daemon and a Seed web app behind a reverse proxy, serving one space at a web domain. The hosted service runs sites for you under *.hyper.media. This guide runs the same three containers on a server you control. Your content and your key stay yours either way, so a hosted site can move to your own server later and back again.
Goal. A site at https://example.org that publishes your space, gets TLS certificates by itself, checks for updates every ten minutes, and can be backed up in one command.
Prerequisites.
A Linux server with about 2 GB of RAM, a public IPv4 address, and ports 80 and 443 free. The deploy script needs glibc 2.25 or newer, which means Ubuntu 18.04, Debian 10, RHEL 8, Fedora 28 or Amazon Linux 2023 and later. It installs Docker and Bun if they are missing.
A domain name you control, with access to its DNS records.
The Seed desktop app with the space you want to publish, and its key. Whoever holds that key can publish to the site.
1. Point the domain at the server
In your DNS provider, add an A record for the domain (the name is @ for the root, or a subdomain such as docs) whose value is the server's IP address. Do this first. The proxy requests a certificate from Let's Encrypt on first start, and that only succeeds once the name resolves to the server.
2. Run the deploy script
Log in as root or as a user with sudo, and run:
curl -fsSL https://deploy.seed.hyper.media | shAt the time of writing that URL redirects to the bootstrap script in the Seed repository, ops/deploy.sh on the main branch. You can fetch it from GitHub directly if you want to read it first. The bootstrap installs Docker and Bun when needed, downloads the deployment engine to /usr/local/lib/seed/deploy.js, installs a seed-deploy command, and starts an interactive wizard.
The wizard asks:
Question | What to answer |
|---|---|
Public hostname | the full URL, |
P2P network | Mainnet, unless you are testing against the team's dev network |
Release channel | Stable ( |
Log level | Info |
Gateway mode | No, for a site that publishes one space; Yes makes the node serve every public account it learns about, like |
Contact email | optional, for security notices |
It then writes the configuration, generates a Caddyfile, pulls the images and starts the containers. On a first deploy it ends with:
Setup complete
Your site is live at https://example.org
Registration URL:
https://example.org/hm/register?secret=…
Copy this URL and paste it into the Seed desktop app
to link your publisher account to this site.Until a space is registered the site renders a "not registered" page. seed-deploy secret prints the registration URL again.
3. Register your space from the Seed app
In the desktop app, open the space, choose Publish Site from the options menu at the top right, paste the registration URL and confirm. The app fetches the site's /hm/api/config, sends its account id and peer addresses to /hm/api/register with the secret, and pushes the space's home document with its related material to the site's daemon. The site records the account, subscribes its daemon to the space, and from then on renders that account's home document at https://example.org.
The secret is consumed on registration. A registered site refuses a different account. To move a site to another space, replace the web configuration file described below with {"availableRegistrationSecret": "<secret>"} (the secret from seed-deploy secret works), run seed-deploy restart so the web app rereads it, and register again.
What is running
Three containers, defined by the compose file the script downloads from the repository.
Container | Image | Ports on the host | Role |
|---|---|---|---|
|
| 80, 443, 443/udp | TLS from Let's Encrypt, then reverse proxy |
|
| 3000 | the Seed web app: pages, the Seed API, site services |
|
| 56000 and 56000/udp | the Seed daemon: storage, indexing, libp2p |
Caddy sends /ipfs/* to the daemon and everything else to the web app. The daemon's own HTTP and gRPC ports (56001 and 56002) are not published on the host. Only the web app reaches them over the container network. The daemon's API has no authentication of its own (see daemon gRPC), so keep it that way and do not open those ports in a firewall. The compose file does not pass -public-only to the daemon. On a site that holds private documents of the registered space, the web app decides what a visitor may see by forwarding their sign-in token to the daemon.
The daemon runs with -p2p.force-reachability-public and -p2p.no-relay, announcing /dns4/example.org/tcp/56000 and the QUIC equivalent, so the libp2p port must be reachable from the internet. Peers, including the desktop app that publishes to the site, connect to it directly.
Everything lives under one directory, /opt/seed by default.
Path | Contents |
|---|---|
| the wizard's answers: |
| the compose file, refreshed on every deploy |
| the proxy configuration and its certificates |
| the site's web configuration: |
| resized images |
| the daemon's data directory: the SQLite database, the blob store, |
Containers run as your user and never as root. All bind mounts carry the :z flag for SELinux hosts.
Day-to-day
The seed-deploy command manages the node.
Command | What it does |
|---|---|
| headless update: refetch the compose file, pull images if anything changed, recreate containers, prune old images; a no-op when nothing changed |
| re-run the wizard with current values as defaults |
| health: containers, disk, cron, certificate expiry, whether the site is registered |
| tail a container's logs |
| the registration URL; the configuration with secrets redacted |
| without redeploying |
| a |
| unpack a backup, optionally edit the configuration, deploy |
| install or remove the update jobs |
| update the deploy script itself |
| remove containers, data and configuration |
Updates. The wizard installs two cron jobs. Every ten minutes it runs upgrade then deploy, so the node follows its release channel. Every hour it prunes unused images older than an hour. The deploy script itself always tracks the main branch, independent of the image channel, so fixes to the orchestration reach every node. SEED_DEPLOY_URL and SEED_REPO_URL redirect the source for testing a branch.
Which version is running. https://example.org/hm/api/version returns the commit, branch and build date of both the web app and the daemon.
Testing a branch. seed-deploy --advanced adds two things: choosing the node directory, so a branch build with its own database migrations lives in /opt/seed-mybranch beside your main node, and a custom image tag. Only one node runs at a time per host, because the container names are fixed. deploy and start refuse to replace a stack owned by another directory, and doctor warns when they disagree.
Metrics. The compose file has a metrics profile with Prometheus and Grafana, served at https://example.org/.metrics when enabled.
Debug pages. The daemon's /debug/* pages answer only to loopback inside its container. docker exec seed-daemon gets you a shell there. seed-deploy logs daemon is usually enough.
Custom domains for hosted sites
If your site is on the hosted service at yoursite.hyper.media, you can still serve it at your own domain without self-hosting. In the Seed app, open the site, choose Publish Custom Domain from the options menu, enter the domain and confirm. Then in DNS either add an ALIAS or flattened CNAME record pointing at yoursite.hyper.media (turn off proxying if you use Cloudflare), or, when your provider cannot do that, an A record pointing at the hosted service's address. At the time of writing hyper.media resolves to 40.160.6.196; check with dig +short A hyper.media before you copy it. Keep the app open while DNS propagates, usually within ten minutes, and the site goes live on the new domain.
The legacy script
website_deployment.sh at the repository root is the previous installer. It is deprecated, with a notice in its header. The deploy script detects installations it made, migrates their configuration, and removes the Watchtower auto-updater they used. Do not use it for new sites.
Where this is going
As of September 2026, the deploy script does not yet report new deployments to the Seed team (a TODO in the source), so security notices depend on the email you entered. A guide for self-hosting behind a different domain setup than a single A record, and for running a node without publishing a site, is not written. The hosted service's own infrastructure (multi-tenant sites, custom domains at scale) lives in a separate repository and is not documented here.
See also
Sites, what a site is in the protocol: the home document's siteUrl, registration, gateways
Network, how the site's daemon syncs with the app that publishes to it
Seed API, everything your new server answers
Keys, the key that publishes to the site
Contributing, for the ops/ deploy tooling
Do you like what you are reading? Subscribe to receive updates.
Unsubscribe anytime