Deploy Soft Serve | (Just Updated) Self-Hosted Git Server over SSH and HTTP, Admin Key Set at Boot, Repos Survive Redeploys

Soft Serve self-hosted Git server over SSH and HTTP, admin key from boot

Deploy Soft Serve | (Just Updated) Self-Hosted Git Server over SSH and HTTP, Admin Key Set at Boot, Repos Survive Redeploys

Just deployed

/soft-serve

Deploy and Host Soft Serve on Railway

Built for an LLM agent to deploy and use. The deploy form has no fields to fill: the admin key is derived from a generated secret, so nobody has to paste an SSH public key or open a setup page. Soft Serve is a self-hostable Git server from Charm: repositories are created and administered over SSH, and cloned and pushed over SSH or HTTP(S), with access tokens, collaborators and a terminal UI.

Upstream status, stated plainly: charmbracelet/soft-serve is archived and sunset by its maintainers, so it receives no further releases or security updates. This template pins the last release (0.12.4) by digest, closes anonymous access by default, and is meant for private or low-risk use. Pick it when you want a tiny Git server that an agent can bootstrap; pick an actively maintained forge (Gitea, Forgejo) when you need updates.

About Hosting Soft Serve

Soft Serve is a single Go binary. On Railway it runs as one service with a volume on /soft-serve holding the repositories, the SQLite database, the SSH host keys and the config, so redeploys keep your repos, your tokens and the same host key (no "host key changed" warning for clients).

The admin account exists from the first boot. Upstream reads its initial admin public key only while the database is empty. The wrapper derives an ed25519 key pair from the generated secret SOFT_SERVE_ADMIN_SEED (private seed = SHA-256 of the secret) and hands the public half to Soft Serve at boot. Anyone who can read the service variables can re-derive the private key with the recipe in the contract below; nobody else can.

Closed by default. Anonymous access is set to no-access and keyless SSH is off, so a fresh deploy exposes nothing without a credential: HTTP requests without a token answer 401, SSH requires the admin key. To open it, set SOFT_SERVE_ANON_ACCESS (read-only, read-write) yourself.

Two public endpoints. SSH (administration and Git over SSH) is public over Railway's TCP proxy at RAILWAY_TCP_PROXY_DOMAIN:RAILWAY_TCP_PROXY_PORT. HTTP(S) Git is public on the service domain, targeting port 23232; HTTP authenticates with an access token created over SSH (token create), sent as the basic-auth username. The git:// daemon is bound to localhost only.

There is no HTTP healthcheck, deliberately: a service with a TCP proxy gets no injected PORT, and Railway's healthcheck then dials the SSH port. The restart policy is ON_FAILURE, and the entrypoint exits non-zero if neither SOFT_SERVE_ADMIN_SEED nor SOFT_SERVE_INITIAL_ADMIN_KEYS is set.

Agent contract

  • Public URL: https://RAILWAY_PUBLIC_DOMAIN (HTTP Git, the generated domain of the soft-serve service); SSH: RAILWAY_TCP_PROXY_DOMAIN port RAILWAY_TCP_PROXY_PORT of the same service
  • Private URL: http://soft-serve.railway.internal:23232 (HTTP Git), soft-serve.railway.internal:23231 (SSH)
  • Credentials: the variable SOFT_SERVE_ADMIN_SEED, set on the soft-serve service; the admin SSH private key is derived from it, never stored. HTTP clients use an access token created with ssh ... token create NAME
  • Auth: SSH with the derived admin key (ssh -i KEY -p PORT HOST repo create NAME); HTTP Git with the token as the basic-auth username and an empty password (https://TOKEN@HOST/NAME.git); anonymous access is off
  • Health: no HTTP healthcheck; readiness is ssh -i KEY -p PORT HOST info returning the server banner, or repo list succeeding
# needs: SITE_URL (public https URL of the soft-serve service), SSH_HOST and SSH_PORT (its RAILWAY_TCP_PROXY_DOMAIN and RAILWAY_TCP_PROXY_PORT), SOFT_SERVE_ADMIN_SEED (its variable); also ssh, openssl, xxd, git, curl
KEY=$(mktemp); WORK=$(mktemp -d); HOST_PATH=${SITE_URL#https://}
printf '302e020100300506032b657004220420%s' "$(printf '%s' "$SOFT_SERVE_ADMIN_SEED" | sha256sum | cut -d' ' -f1)" | xxd -r -p | openssl pkey -inform DER -out "$KEY"; chmod 600 "$KEY"
SS="ssh -i $KEY -p $SSH_PORT -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR $SSH_HOST"
$SS repo create agent-check
TOKEN=$($SS token create agent-check | tail -1)
curl -s -o /dev/null -w "read without key: %{http_code}\n" "$SITE_URL/agent-check.git/info/refs?service=git-upload-pack"
curl -s -o /dev/null -w "write without key: %{http_code}\n" -X POST -H 'content-type: application/x-git-receive-pack-request' --data-binary 0000 "$SITE_URL/agent-check.git/git-receive-pack"
curl -s -o /dev/null -w "write wrong key: %{http_code}\n" -u wrong: -X POST -H 'content-type: application/x-git-receive-pack-request' --data-binary 0000 "$SITE_URL/agent-check.git/git-receive-pack"
git clone -q "https://$TOKEN@$HOST_PATH/agent-check.git" "$WORK/r"
git -C "$WORK/r" -c user.name=agent -c user.email=agent@example.com commit -q --allow-empty -m "agent check"
if git -C "$WORK/r" push -q origin HEAD:main; then echo "push with token: ok"; fi
git clone -q "https://$TOKEN@$HOST_PATH/agent-check.git" "$WORK/r2"
git -C "$WORK/r2" log --oneline | head -1
$SS repo list
$SS repo delete agent-check
rm -rf "$KEY" "$WORK"

A fresh deploy prints the created repository, read without key: 401, write without key: 401, write wrong key: 401, push with token: ok, the agent check commit from the second clone, a repository list that includes agent-check, and then deletes the check repository.

Why Deploy Soft Serve on Railway?

Railway is a singular platform to deploy your infrastructure stack. Railway will host your infrastructure so you don't have to deal with configuration, while allowing you to vertically and horizontally scale it.

By deploying Soft Serve on Railway, you are one step closer to supporting a complete full-stack application with minimal burden. Host your servers, databases, AI agents, and more on Railway.

  • Agent-ready - no deployer fields, a contract block and one script that proves SSH admin, token creation, HTTP push and refusal without a key.
  • Closed by default - anonymous access off, keyless SSH off, HTTP answers 401 without a token.
  • Data survives redeploys - a volume on /soft-serve keeps repos, tokens and the SSH host key; the admin key stays the same.
  • Light - one small Go container with no database to run.
  • Pinned - the image is pinned by digest, which matters because upstream is archived and will not change under you.

Common Use Cases

  • A private Git remote for an agent - create repositories and push work over SSH or HTTP without a full forge.
  • Self-hosted Git for a small team - SSH-key based access, collaborators and a terminal UI.
  • Mirror and backup target - a cheap remote for important repositories.
  • Git over SSH with no web admin to secure - administration is only over SSH with a key.

Dependencies for Soft Serve Hosting

  • None. Soft Serve keeps its state in SQLite and plain Git repositories on the volume. A custom domain is optional.

Deployment Dependencies


Template Content

More templates in this category

View Template
Rocky Linux
Hosted Rocky Linux 9 workspace with SSH and persistent storage. 🚀

codestorm
51
View Template
Foundry Virtual Tabletop
A Self-Hosted & Modern Roleplaying Platform

Lucas
71
View Template
Letta Code Remote
Run a Letta Code agent 24/7. No inbound ports, just deploy.

Letta
52