Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

Document deploying to a server, one guide per path

b8e7a5679847cf4cdda0754f6154805585e1be96 · Keenan Nemetz · 3d ago · parent b86148d1

Document deploying to a server, one guide per path

The guides are for someone who has a machine and a domain and has not
deployed a forge before, so they start from what to have ready and end at a
working https address with one instance admin.

Each ends with the same three things: where first-boot output is, what a
certificate warning means, and that pushing needs port 2222 reachable and a
key that belongs to an account.

The index page carries what the per-cloud pages should not repeat: backups
are separate from a cloud snapshot because secrets.key must not travel with
the data, and upgrades are a package install rather than an apply.

docs/deploy/docker.md +144

@@ -0,0 +1,144 @@
1 # Vole in a container
2
3 Vole is one process with its own SSH listener, so it fits in one container: no
4 `sshd`, no sidecar, and one volume holding the whole instance.
5
6 ## What you need
7
8 - Docker with the Compose plugin.
9 - A clone of the Vole repository, because the image compiles from source.
10 - A DNS name, and something to terminate TLS. Vole can do TLS itself — see
11 [Let Vole terminate TLS](#let-vole-terminate-tls) — but the default here is a
12 proxy on the same host, which is how most container hosts are already set up.
13 - Your SSH public key.
14
15 ## Build the image
16
17 From the repository root:
18
19 ```sh
20 docker build -f deploy/docker/Dockerfile -t vole:local .
21 ```
22
23 The first build compiles Vole and takes a few minutes; later ones reuse the
24 layers. Nothing about the running instance is in the image: state lives in a
25 volume, and the configuration is a file you mount.
26
27 ## Configure and start
28
29 ```sh
30 cd deploy/docker
31 cp vole.toml ./vole.toml
32 $EDITOR ./vole.toml # set public_url to https://forge.example.com
33 docker compose up -d
34 ```
35
36 `public_url` is the one setting that must be right before anyone signs in:
37 browsers bind a passkey to that address, and changing it later means re-enrolling
38 everyone.
39
40 The default ports are 2222 for Git over SSH and 8080 published on loopback, for a
41 proxy. Port 2222 is a plain TCP port, not HTTP: a proxy cannot route it, so
42 publish it directly.
43
44 Create the first account and make it an instance admin:
45
46 ```sh
47 docker compose exec -T vole admin account create admin --key - < ~/.ssh/id_ed25519.pub
48 docker compose exec vole admin account role admin admin
49 ```
50
51 Pushing works immediately, because the account and the key are written together.
52 The browser needs a passkey, which that command prints when the account has none:
53
54 ```sh
55 docker compose exec vole admin account invite admin
56 ```
57
58 Keep the link private: it is the one thing that grants browser access.
59
60 ## Put TLS in front
61
62 The smallest thing that works is Caddy on the same host:
63
64 ```caddyfile
65 forge.example.com {
66 reverse_proxy 127.0.0.1:8080 {
67 header_up Host {host}
68 }
69 }
70 ```
71
72 Caddy sets `X-Forwarded-For` itself, and `vole.toml` names that header, so the
73 per-client clone limit and the sign-in rate limits see the real address. Whatever
74 proxy you use has to keep the `Host` header unchanged: hosted sites are routed by
75 host name.
76
77 ## Let Vole terminate TLS
78
79 If nothing else on the host serves 80 and 443, Vole can hold the certificate:
80
81 ```toml
82 http_addr = ":80"
83 https_addr = ":443"
84 public_url = "https://forge.example.com"
85 acme_email = "you@example.com"
86 ```
87
88 and in `compose.yaml`, publish `80:80` and `443:443` instead of the loopback 8080.
89 The container runs as an unprivileged user, so add the capability for the two low
90 ports:
91
92 ```yaml
93 cap_add:
94 - NET_BIND_SERVICE
95 ```
96
97 The health check in the packaged `compose.yaml` asks `/healthz` on port 8080,
98 which is the proxy arrangement above. With `https_addr` set, port 80 answers the
99 certificate challenge and redirects everything else, so either point the check at
100 `https://127.0.0.1/healthz` or remove it.
101
102 ## CI runners stay outside
103
104 A runner executes repository code in containers and needs a real OCI runtime, so
105 it does not belong in this container. Run it on the host or another machine and
106 point it at the instance:
107
108 ```sh
109 vole runner --remote ssh://git@forge.example.com:2222 --container-runtime podman
110 ```
111
112 See [Runners](../runner.md).
113
114 ## Where the data is, and how to get it out
115
116 The volume holds everything: repositories, the account database, CI history,
117 logs, LFS objects, the SSH host key, and the key that seals repository secrets.
118
119 ```sh
120 docker compose exec vole admin account backup /var/lib/vole/users.db.bak
121 docker compose cp vole:/var/lib/vole/users.db.bak ./users.db.bak
122 ```
123
124 Two files must be copied out separately and kept apart from that backup: the SSH
125 host key, `data/ssh_host_ed25519_key`, and the secrets key, `data/secrets.key`. A
126 backup holding the data directory and that key is a backup of plaintext. The full
127 procedure is in [Operations § Back up an instance](../operations.md#back-up-an-instance).
128
129 A bind mount instead of a named volume works, but the directory has to be writable
130 by the image's user: `sudo chown -R 999:999 /srv/vole`.
131
132 ## Upgrading
133
134 ```sh
135 docker compose exec vole admin account backup /var/lib/vole/users.db.bak
136 docker compose cp vole:/var/lib/vole/users.db.bak ./users.db.bak
137 git pull
138 docker build -f deploy/docker/Dockerfile -t vole:local .
139 docker compose up -d
140 ```
141
142 Back up first: the account database is migrated in place the first time a newer
143 binary reads it, and that migration cannot be undone. A new image also reads the
144 configuration file you mounted, so settings survive.