Quickstart (Docker)
-
Get
docker-compose.yml. There are two ways to do this. The shorter path: takedocker-compose.ymlon its own, with no clone. Create.envfirst (step 2 below); Compose reads it viaenv_fileand errors before doing anything else if it is missing. With.envin place,docker compose uppulls and runs the published image directly. The image isghcr.io/greenwichsteps/burnside-relay, published for bothlinux/amd64andlinux/arm64, so it runs on an ARM box or an Apple Silicon machine without emulation. Clone the repository instead if you want to read the source or run a modified relay:A published image exists either way, so plaindocker compose upstill pulls it rather than building your checkout. To run your own code, build explicitly withdocker compose up --build(ordocker compose buildfirst). One caveat on the published image while it is still at 0.1.1: it was built before the relay started shipping its licence inside the image, so the copy you pull today does not contain the AGPL text. Building from a clone with--builddoes include it. The next release replaces the published tags with an image that carries it. -
Create a data directory and an
.envfile next todocker-compose.yml: -
Start it:
-
Put a TLS reverse proxy (Caddy, nginx, or Cloudflare Tunnel) in front and
point
PUBLIC_BASE_URLat the https origin it serves. HTTPS is required for the admin panel: its session cookie is named__Host-bs_admin, and a browser takes a__Host-cookie only from an origin it treats as secure. -
Claim the admin panel at
https://feedback.example.com/admin, using theADMIN_SETUP_TOKENand a password of at least 8 characters. Add a project, set its allowed origins, and connect Linear, GitHub, or GitLab. -
Install the widget on your site with the relay origin:
Quickstart (no Docker)
This path needs Node 20 or later. The Docker image above carries its own runtime, so the requirement only applies here. The npm package is not published yet. It goes out with the first release, sonpm i -g @burnsidesteps/relay returns a 404 today. Until then, build from a clone using the second set of
commands below. Both are listed because the first is what you will use once the release lands.
Put the settings in a .env file next to where you run it, the same file the Docker path uses:
ADMIN_SETUP_TOKEN=... burnside-relay writes the token
into your shell history, where it stays after you have finished setting up, and it is visible in
the process list to anyone else on the machine while the command runs. The token claims the admin
panel and is not retired after first use, so it is worth the extra line.
Run it under a process supervisor (systemd, pm2) and front it with a TLS proxy
as above.
Building from a clone
relay/public is not in the repository (it is
generated, and gitignored), so a server built without the first three starts cleanly, logs that it
is listening, and then answers 404 for /, /admin and /widget.js. Nothing fails along the way,
which is what makes it worth stating: every command exits 0.
If you would rather not remember the order, pnpm --dir relay prepack runs exactly these four.
This produces the same server the Docker image runs. The image runs the same four steps in its own
build, which is why the Docker path needs none of this.
Configuration
All configuration is via environment variables.Data and backups
DATA_DIR/config.json holds your project config, integration tokens, the admin
password hash, and any Pro licence. It is written with 0600 permissions. Back
up the whole DATA_DIR to preserve config and stored screenshots. Keep the
directory private.
Screenshots
Only GitHub destinations store screenshots here. Linear and GitLab upload through their own APIs, so nothing of theirs lands on disk and retention does not apply to them. Screenshots are stored on disk underDATA_DIR/shots/ and served from
/api/shot/.... There is nothing external to configure. If you set
SHOT_SIGNING_KEY, the URLs are signed so only links the relay generated can be
fetched.
Screenshots are kept indefinitely unless you say otherwise. Set SHOT_RETENTION_DAYS to a
positive number of days and the relay deletes anything older, sweeping once at startup and every
six hours after that, logging a line whenever it removes something. This is yours to decide and
your licence does not affect it: the disk is yours.
Compression
The relay compresses its own static assets: brotli when the browser offers it, gzip otherwise, for JavaScript, CSS, HTML, JSON and SVG. It matters most forwidget.js, which every visitor to every
page carrying the widget downloads, and which is about 3.6 times larger uncompressed.
You do not need to configure anything, and you should not need to turn it on in your reverse proxy
either. Neither nginx nor Caddy compresses proxied responses by default, which is why this is done
here rather than left to them. If you have already enabled compression in your proxy, it will see
a response that is already encoded and pass it through.
Each file is compressed once and the result is kept in memory until the file changes, so the cost
is paid on the first request after a deploy rather than on every request.
Health checks
GET /api/health reports whether the relay can reach its own storage. It needs no credential, so
an uptime monitor can call it directly:
checked names what was probed, failing names what did not answer within three seconds. Any
failure returns 503 with the same body shape, so a monitor can alert on the status code alone
and read failing to know which part is unwell.
Point your uptime check here rather than at the relay’s root page. That page is a static file and
renders identically whether or not the relay can do anything at all.
The result is cached for five seconds, so a monitor polling faster than that gets the same answer
twice, and a recovery can take up to five seconds to show.
Rotating a secret
Two of the values above can be changed after the relay is running, and each costs something specific. Neither cost is recoverable afterwards, so it is worth knowing before you rotate rather than after.ADMIN_SETUP_TOKEN
This gates claiming the admin panel. Rotating it is cheap: set a new value and restart the relay. Nothing already working stops working, because the token is only read when the panel is claimed. The more useful action is usually to remove it. Once the panel has been claimed, anADMIN_SETUP_TOKEN that is still set is a credential with nothing left to protect, and it becomes
a live one again the moment the stored credentials are deleted. If you no longer need to claim the
panel, unset it.
SHOT_SIGNING_KEY
This signs screenshot URLs. Rotating it invalidates every screenshot link already filed in your tracker: the signatures in those URLs were made with the old key, so they stop verifying and the images stop loading. The issues keep their text, and the image is still in your storage, but the links in them are dead and there is no way to re-sign an issue body that has already been written. So rotate this one only when you have a reason to believe the key itself is exposed. If you are rotating it as routine hygiene, you are trading every historical screenshot for very little. The same applies to changing its encoding: hex and standard base64 are both accepted, and the same 32 bytes written either way produce the same key, but a different value is a different key and retires the old links exactly as a rotation would.If you think a secret has leaked
Rotate first and investigate afterwards. The order matters more than the tidiness.ADMIN_SETUP_TOKEN: unset it if the panel is already claimed. That closes the route with no side effects at all.SHOT_SIGNING_KEY: rotate, accepting the cost above. A leaked signing key means anyone holding it can mint URLs for any screenshot in your storage, which is worse than losing the old links.- The admin password: change it from inside the panel. Note that a stolen session cookie currently survives a password change until it expires, so if you believe a session was taken rather than the password, treat the relay as compromised until the session TTL has passed.
- A destination token (the GitHub, GitLab or Linear token you configured): revoke it at the provider, not here. Removing it from the relay stops the relay using it and does nothing about anyone else who has it.