- Shell 72%
- Docker 28%
Self-hosted Tangled knot on Fly.io #
This repo contains the configuration I used to self-host a Tangled knot on Fly.io.
It is not a polished installer or reusable tool. It is a working example of one Fly-based knot deployment: a Docker image that builds the Tangled knot binary from a pinned upstream tag, runs the knot HTTP server behind Fly HTTPS, exposes Git-over-SSH, and keeps knot state on a Fly volume.
If you want to copy this setup, expect to read through the files, replace the deployment-specific values, and adapt it to your own domain, Fly app, region, and Tangled account.
How this setup works #
- Fly serves HTTPS traffic to the knot HTTP/XRPC server on internal port
5555. - Fly exposes public SSH port
22and forwards it tosshdinside the container on port2222. - Git-over-SSH runs as the
gituser. - The knot database, repositories, generated SSH host keys,
authorized_keys, and MOTD are stored on a Fly volume mounted at/home/git. sshdasks the knot for authorized keys with:AuthorizedKeysCommand /usr/local/bin/knot keys -o authorized-keys.
The important files are:
Dockerfile— builds Tangled core at the explicitKNOT_VERSIONtag and prepares the runtime image.docker/entrypoint.sh— initializes persisted SSH/git state and starts the knot server plussshd.docker/sshd_config— SSH configuration for Git-over-SSH.docker/keys-wrapper.sh— wrapper used byAuthorizedKeysCommand.docker/guard-wrapper.sh— wrapper used as thegituser's forced shell.fly.toml— Fly app, services, ports, environment, and volume mount.
Values you would need to change #
This repository's fly.toml contains concrete values from my deployment. If you copy this setup, replace them with your own:
- Fly app name:
app - Fly region:
primary_region - Knot hostname:
KNOT_SERVER_HOSTNAME - Fly volume name:
[[mounts]].source - Tangled owner DID: set as the Fly secret
KNOT_SERVER_OWNER, not committed to the repo - Knot version:
KNOT_VERSIONinDockerfileandfly.toml[build.args]
For example, your local planning variables might look like:
APP_NAME="my-knot"
REGION="iad"
VOLUME_NAME="knot_data"
HOSTNAME="knot.example.com"
OWNER_DID="did:plc:REPLACE_ME"
KNOT_VERSION="v1.14.1-alpha"
Rough setup flow #
This is the shape of the setup I used. Treat it as notes to adapt, not a copy/paste installer.
Create the Fly app and persistent volume:
fly apps create "$APP_NAME"
fly volumes create "$VOLUME_NAME" --region "$REGION" --size 10 --app "$APP_NAME"
Set the Tangled account DID that owns the knot:
fly secrets set KNOT_SERVER_OWNER="$OWNER_DID" --app "$APP_NAME"
Deploy the app:
fly deploy --app "$APP_NAME" --build-arg KNOT_VERSION="$KNOT_VERSION"
Ask Fly to provision TLS for your knot hostname and inspect the IPs you need for DNS:
fly certs add "$HOSTNAME" --app "$APP_NAME"
fly ips list --app "$APP_NAME"
Then create the requested DNS records with your DNS provider.
For SSH on public port 22, I found that a dedicated Fly IPv4 address was necessary. Fly's shared IPv4 proxying can be unsuitable for OpenSSH on port 22 in some setups.
If there's a way to serve knot SSH traffic over IPv6 I didn't find it, if one exists that may be a workaround to paying $2/mo to fly.io. However, it's worth noting that Fly's current policy is not to bill for monthly invoices under $5, so if this is your only Fly project, it will likely be free.
Registering with Tangled #
Once the service is deployed and DNS resolves, register or verify the knot hostname in Tangled settings. After that, create a repository that uses the knot and push to the SSH remote Tangled gives you.
A remote will generally look like:
git remote add origin git@knot.example.com:owner.example/repo-name
git push -u origin main
One local gotcha: if your shell sets GIT_SSH_COMMAND for another host, make sure it does not force Tangled pushes through that host. For a one-off push, you can clear it:
env -u GIT_SSH_COMMAND git push -u origin main
Updating the knot version #
The upstream Tangled core version is explicit rather than floating. At the moment this setup builds KNOT_VERSION=v1.14.1-alpha.
There are two places to check when changing it:
Dockerfilehas the default build argument:ARG KNOT_VERSION=...fly.tomlpasses the same value under[build.args]
To update, choose the upstream tag you want, update both values, and deploy:
git ls-remote --tags https://tangled.org/@tangled.org/core 'v*'
fly deploy --app "$APP_NAME"
You can also override the configured value for a one-off build:
fly deploy --app "$APP_NAME" --build-arg KNOT_VERSION="v1.14.1-alpha"
After deploy, the container writes the configured tag and checked-out commit to /home/git/.knot.env as KNOT_VERSION and KNOT_REVISION, which is useful when checking what is actually running.
Operational notes #
/home/gitmust be persistent. Losing it means losing repositories, knot database state, generated SSH host keys, and git user SSH state.- The knot SQLite database lives at
/home/git/knotserver.db. - Generated SSH host keys live under
/home/git/.ssh/host_keysso the SSH host identity survives deploys and restarts. - Password login and root SSH login are disabled.
- Fly volumes are tied to regions and machines. For a small personal knot, a single persistent volume is the simplest setup.
Relationship to Tangled docs #
The Tangled self-hosting guide documents NixOS, community Docker, and manual setup options. This repository follows the manual setup shape inside a container: build the knot binary from the configured KNOT_VERSION tag in tangled.org/core, run knot server, configure sshd with Tangled's authorized-keys command, and persist /home/git.