Seed is open source, which means you do not have to wait for every idea, experiment, or local workflow improvement to be designed and shipped by the core team before you can use it. If you can change the code, you can run that change on your own site. That is one of the most powerful parts of self-hosting: your deployment can become a living place for your own product instincts, prototypes, and community-specific needs.
This does not mean forking away from the project forever. The goal is to keep a small layer of your own changes on top of upstream Seed: follow upstream closely, rebase regularly, and publish your own images whenever your branch changes. You get the best of both worlds: the ongoing work from the core project, plus the freedom to develop and deploy features that matter to you right now.
Why deploy from your own fork?
The official Docker images are the right default for most self-hosted installs. They are stable, predictable, and maintained by the Seed team. But they intentionally represent what the core project has already accepted and released. If you want to try a new feature, tune the interface for your own publishing workflow, add an integration, or experiment with a behavior that may never belong in the default product, the official images are not enough.
Running from your own fork turns open source from “I can read the code” into “I can shape the software I use.” Your site becomes a place where you can test ideas in production for yourself, without asking the core team to prioritize them first. Later, if an idea proves useful, you can upstream it as a pull request; if it remains personal, it can simply stay in your fork.
What this setup gives you
This guide shows how to run a self-hosted Seed site from your own fork instead of the official Docker images. The setup keeps your fork's main branch rebased on upstream Seed, builds your own Docker images with GitHub Actions, publishes them to GHCR, and lets your server auto-update from those images.
The result is a repeatable deployment loop:
develop a change in your fork;
rebase your fork on upstream
main;build and publish fork-owned Docker images;
let your server pull and run those images;
roll back to an immutable SHA tag if something goes wrong.
The examples below use:
Fork:
horacioh/seedUpstream:
seed-hypermedia/seedDeployment branch:
mainWeb image:
ghcr.io/horacioh/seed-web:mainSite/daemon image:
ghcr.io/horacioh/seed-site:mainSite domain:
https://seed.horacioh.com
Replace those values with your own GitHub username, fork, and domain.
1. Create and maintain your fork
Create a fork of the Seed repository and add the upstream remote locally:
git remote add upstream https://github.com/seed-hypermedia/seed.git
git remote add myfork git@github.com:<your-user>/seed.gitUse your fork's main branch as the deployment branch. Your changes should live as commits on top of upstream main.
To manually rebase your fork:
git fetch upstream main
git fetch myfork main
git switch main
git rebase upstream/main
git push --force-with-lease myfork mainDo not auto-resolve rebase conflicts. If conflicts happen, resolve them manually, run checks, and push with --force-with-lease only after the branch is healthy.
2. Add a daily rebase workflow
Add a GitHub Actions workflow that keeps your fork's main branch rebased on upstream main.
Important details:
Checkout your fork's
main.Add
seed-hypermedia/seedasupstream.Rebase onto
upstream/main.Push with
--force-with-lease.If the rebase conflicts, let the workflow fail.
If you trigger an image build from this workflow, explicitly target your fork with
--repo <your-user>/seed.
Example dispatch command after a successful rebase:
gh workflow run custom-ghcr-images.yml --repo <your-user>/seed --ref mainBranch protection note: this workflow needs permission to force-update main. Configure branch protection accordingly, or use a separate deployment branch instead.
3. Publish custom Docker images to GHCR
The simplest artifact store is GitHub Container Registry (GHCR). Public GHCR images are free and Docker-native.
Create a custom image workflow based on Seed's release-docker-images.yml, but change:
Trigger: run on pushes to your fork's
main.Tag: publish
maininstead oflatest.Registry target: publish to
ghcr.io/<your-user>/...instead of Docker Hub.
Publish both images from the same commit:
ghcr.io/<your-user>/seed-web:main
ghcr.io/<your-user>/seed-site:mainAlso publish immutable rollback tags:
ghcr.io/<your-user>/seed-web:sha-<commit-sha>
ghcr.io/<your-user>/seed-site:sha-<commit-sha>For Horacio's fork, the image refs are:
ghcr.io/horacioh/seed-web:main
ghcr.io/horacioh/seed-site:mainWhy publish both web and site? Because frontend and daemon changes can move together after upstream rebases. Running a custom web image against an unrelated official daemon image can create compatibility issues.
4. Make the deploy script support custom images
The deploy compose file should allow full image refs. Instead of hardcoding only official images:
image: seedhypermedia/web:${SEED_SITE_TAG:-latest}
image: seedhypermedia/site:${SEED_SITE_TAG:-latest}use overridable refs:
image: ${SEED_WEB_IMAGE:-seedhypermedia/web:${SEED_SITE_TAG:-latest}}
image: ${SEED_SITE_IMAGE:-seedhypermedia/site:${SEED_SITE_TAG:-latest}}Your deploy config should store:
{
"deploy_url": "https://raw.githubusercontent.com/<your-user>/seed/main/ops",
"compose_url": "https://raw.githubusercontent.com/<your-user>/seed/main/ops/docker-compose.yml",
"release_channel": "main",
"web_image": "ghcr.io/<your-user>/seed-web:main",
"site_image": "ghcr.io/<your-user>/seed-site:main"
}This keeps seed-deploy upgrade and seed-deploy deploy pointed at your fork instead of switching back to the official deploy script.
5. Bootstrap or reconfigure the server
On the server, run the deploy script from your fork:
curl -fsSL https://raw.githubusercontent.com/<your-user>/seed/main/ops/deploy.sh | \
SEED_DEPLOY_URL=https://raw.githubusercontent.com/<your-user>/seed/main/ops shFor an existing install, back it up first:
seed-deploy backupThen reconfigure it with your fork deploy source:
SEED_DEPLOY_URL=https://raw.githubusercontent.com/<your-user>/seed/main/ops \
seed-deploy deploy --reconfigureUse values like:
domain: https://your-seed-site.example.com
network: mainnet
release_channel: main
web_image: ghcr.io/<your-user>/seed-web:main
site_image: ghcr.io/<your-user>/seed-site:mainFor Horacio's deployment:
domain: https://seed.horacioh.com
network: mainnet
release_channel: main
web_image: ghcr.io/horacioh/seed-web:main
site_image: ghcr.io/horacioh/seed-site:mainIf your GHCR packages are private, log in from the server before deploying:
echo "$GHCR_TOKEN" | docker login ghcr.io -u <your-user> --password-stdinPublic GHCR packages do not need Docker login.
6. Deploy and verify
Run a deploy:
seed-deploy deployVerify the running containers:
seed-deploy doctor
docker inspect seed-web --format '{{.State.Status}} {{.Config.Image}}'
docker inspect seed-daemon --format '{{.State.Status}} {{.Config.Image}}'Expected output should include your GHCR images:
running ghcr.io/<your-user>/seed-web:main
running ghcr.io/<your-user>/seed-site:mainCheck logs if needed:
seed-deploy logs web
seed-deploy logs daemon
seed-deploy logs proxy7. Confirm automatic updates
The deploy tool installs cron jobs that periodically run upgrade and deploy. Check them with:
crontab -l | grep seed-To test auto-update:
Push a small change to your fork's
main.Wait for the GHCR image workflow to publish new images.
Wait for cron, or trigger deploy manually:
seed-deploy deployConfirm the image IDs or container start times changed:
docker inspect seed-web --format '{{.Image}} {{.State.StartedAt}}' docker inspect seed-daemon --format '{{.Image}} {{.State.StartedAt}}'
8. Roll back to a known-good commit
Because the workflow publishes immutable sha-<commit> tags, rollback is just a config change.
Pick a known-good commit SHA and set both images to matching tags:
{
"web_image": "ghcr.io/<your-user>/seed-web:sha-<commit-sha>",
"site_image": "ghcr.io/<your-user>/seed-site:sha-<commit-sha>"
}Then deploy:
seed-deploy deploy
seed-deploy doctorReturn to the moving channel by setting both images back to :main.
Troubleshooting
The rebase workflow looks in the wrong repository
If gh workflow run reports that it is looking in seed-hypermedia/seed, pass the repository explicitly:
gh workflow run custom-ghcr-images.yml --repo <your-user>/seed --ref mainBackend tests fail with models/*.gguf: no matching files found
Make sure your custom image workflow includes the same GGUF model cache/download step as Seed's release workflow before running backend tests or building the site image.
The server still runs official images
Inspect the deployed config:
seed-deploy configMake sure web_image and site_image are full GHCR refs. Then redeploy:
seed-deploy deployDocker cannot pull from GHCR
If your GHCR package is private, run docker login ghcr.io on the server. If you do not need private images, make the GHCR packages public.
Summary
The complete flow is:
Keep your fork's
mainrebased on upstreammain.Build and publish
webandsiteimages from your fork to GHCR.Configure your server to use your fork's deploy source.
Configure full GHCR image refs for both services.
Let
seed-deploycron pull new:mainimages automatically.Use
sha-<commit>tags when you need a pinned rollback.
Do you like what you are reading? Subscribe to receive updates.
Unsubscribe anytime