Running a site

Deploying

A Gansey deployment is four things:

  • the gansey executable for its platform (Linux x86_64, Linux aarch64 or macOS), with the admin and the bundled assets built in;
  • its configuration: a gansey.toml, environment variables, or both (see Configuration);
  • a Postgres database (14 or newer) that holds this site and nothing else;
  • a data/ folder the process can write to.

One binary serves one site. The public pages, the JSON API under /api and the admin under /admin/ all come from the same process, so two sites means two processes, two databases and two data folders.

The data folder

With the default settings everything the site writes lives under data/:

PathWhat it holdsSetting
data/media/uploads and their generated sizes[media].root
data/module-files/files modules keep, such as form uploads and release files[modules].files_dir, else module-files beside the media folder
data/modules/installed modules, one folder each[modules].dir
data/backups/backups, one folder each[backups].dir
data/logs/daily JSON log files[logs].dir
data/hub-public-keythe hub's release signing key, pinned on first usebeside the media folder
data/licence-check.jsonthe last licence answer, kept for the grace periodbeside the media folder
data/signing.keythe site's own signing key, only on sites that sign files[signing].key_file

Relative paths are resolved against the process's working directory, not the folder gansey.toml sits in, and gansey.toml is looked for in the working directory unless --config names it. Start the binary from the site's folder, or use absolute paths throughout.

Binding and ports

The server listens on [server].bind and [server].port, 127.0.0.1:8080 by default. Gansey speaks plain HTTP and does not terminate TLS itself. Keep it on the loopback address when the reverse proxy runs on the same machine; bind 0.0.0.0 only when the proxy reaches it over a private network or from outside a container.

gansey serve --migrate --bind 127.0.0.1 --port 8080

The public URL

Set [auth].public_url (or GANSEY_PUBLIC_URL, or the short form GANSEY_URL) to the address visitors use, such as https://example.test. Gansey uses it for links in emails, canonical link tags, the sitemap and the Sitemap line in robots.txt, to decide whether the session cookie is marked Secure, and as part of the session cookie's name. Without it the site falls back to http://<bind>:<port>, which is right locally and wrong behind a proxy. Changing it later signs everyone out (see Security).

Behind a reverse proxy

Put a reverse proxy in front of the site to terminate TLS and forward requests to the bind address. Gansey does not read X-Forwarded-Proto or X-Forwarded-Host: it learns its public address from public_url alone. Configure the proxy to:

  • pass the original Host header through. When a browser does not send Sec-Fetch-Site, core compares the Origin header with Host to spot cross-site posts to module routes.
  • set X-Forwarded-For to the visitor's address, replacing any value the visitor sent. Gansey reads the first address in X-Forwarded-For (or X-Real-IP when that is absent), and only when the connection itself comes from a loopback or private-network address; a connection from a public address is taken at its word. That address is what module routes are told and what their rate limits count.
  • accept request bodies large enough for uploads: up to 64 MB for the media library, 256 MB for theme kit imports and 512 MB for module routes.

Gansey compresses responses itself (gzip and brotli), so the proxy does not need to.

The container image

The repository's Dockerfile builds a Linux image that runs gansey serve --migrate as an unprivileged gansey user in /var/lib/gansey. It listens on 0.0.0.0:8080, keeps its data in /var/lib/gansey/data (mount a volume there), ships pg_dump and pg_restore for Postgres 17 and git for modules installed from a repository, and checks /api/health every 10 seconds. Configure it with environment variables such as DATABASE_URL, GANSEY_ENV and GANSEY_URL. The binary sits in /usr/local/bin, which the gansey user cannot write to, so a container cannot update itself: replace the image instead.

Running as a service

Whatever starts the process:

  • run gansey serve --migrate from the site's folder, as an unprivileged user that owns the data folder. Keeping --migrate means a new version applies its migrations as it starts.
  • restart it when it exits.
  • let that user write to the folder that holds the executable, if the site updates itself. Installing an update renames files there.
  • stop it with SIGINT where you can. The server finishes requests in flight on SIGINT (Ctrl+C); other stop signals end it at once.

An update installed from the admin replaces the running process with the new version in place (the process id stays the same). After gansey self-update on the command line, restart the service yourself.

Checking an installation

gansey doctor checks what the site needs and prints one line per check: the database connection, how many migrations have run, whether any users exist, the media folder, the logs folder, pg_dump, pg_restore and tar on the PATH, whether email is set up, and the public URL. It exits with status 1 when anything fails, so it fits in a deploy script.

gansey --config /srv/example/gansey.toml doctor