Skip to content

Latest commit

 

History

History
273 lines (213 loc) · 14.8 KB

docker.md

File metadata and controls

273 lines (213 loc) · 14.8 KB

Install with Docker

A Docker node is two containers, Postgres and the node, started by Docker Compose from three files in a directory of their own. Nothing is compiled on your machine.

Requirements

  • Docker Engine or Docker Desktop with Compose 2.24 or later, on x86-64 or arm64.
  • bash and curl, for the ./stuga operator commands.

Install

curl -fsSL https://github.com/stuga-dev/stuga/releases/latest/download/install.sh | bash

This makes a stuga directory, downloads the release's compose.yml, env.example and stuga into it, writes .env with this machine's address on the network as PUBLIC_ORIGIN and a random database password, starts the stack and waits until the node serves. It ends by printing the link that sets the node up, with its setup code in it. Continue with Getting started.

Options go after bash -s --: --dir <dir>, --version <1.2.3>, --port <n>, --origin <url> when this machine's address is not the one other devices use, and --local-only to serve this machine only.

By hand, the same steps are:

mkdir stuga && cd stuga
for f in compose.yml env.example stuga; do
  curl -fsSLO "https://github.com/stuga-dev/stuga/releases/latest/download/$f"
done
chmod +x stuga
cp env.example .env
echo "PUBLIC_ORIGIN=http://192.168.1.50:8787" >> .env   # this machine's address on the network
./stuga db-password                                     # a random database password, in .env
docker compose up -d

For a specific release, use releases/download/v<version>/ in place of releases/latest/download/. Until the node serves, it answers with a page that says it is starting. ./stuga status reports ready yes once it serves, and the setup link while nobody has claimed the node.

What is where

Path Holds
compose.yml The stack, with its three images pinned to one release. An upgrade replaces it, so do not edit it.
.env Your settings and the database password, readable only by you. Compose reads it, and passes it to the node.
stuga The operator commands.
compose.rollback.yml Only while the stack runs another release than compose.yml names, after a restore or a failed upgrade: keeps the node, Postgres and remote access on it until the next upgrade, through COMPOSE_FILE in .env. See Roll back.
data/node/ The node's data directory (DATA_DIR): document snapshots, media, per-actor SQLite stores, the signing key and secrets.
the stuga_pgdata volume The Postgres cluster. docker volume inspect stuga_pgdata shows where Docker keeps it.
the stuga_remote volume What the node and the remote access connector share: its settings, credential and socket. The node writes it again, so it is not backed up.
backups/ Backups: the node's daily ones, the one it takes before an upgrade, and ./stuga backup's.

Postgres is on the internal stuga_db network, which only the node joins: it publishes no port and has no route out.

The node's log is docker compose logs node, Postgres's is docker compose logs postgres, and the remote access connector's is docker compose logs remote. Add -f to follow one.

Settings

Put settings in .env, then run docker compose up -d. A docker compose restart keeps the old environment. Any node variable in Configuration can go in .env, except the ones the stack sets itself. compose.yml sets DATABASE_URL, DATA_DIR, BIND, PORT, BACKUP_DIR and the remote access hints (STUGA_REMOTE_DIR, STUGA_CONNECTOR_REQUEST, STUGA_CONNECTOR_STATUS, STUGA_REMOTE_GID, STUGA_REMOTE_CONNECTOR_UID) inside the container, and a value for them in .env has no effect there. Remote access is off until .env sets STUGA_REMOTE_SERVICE, which switches the rest on. The image sets PG_BIN and the packaging hints, so leave those out too.

These variables belong to the stack, not to the node:

Variable Default
HOST_BIND 0.0.0.0 The host address Docker publishes the node on. 127.0.0.1 keeps it to this machine; this machine's network address keeps it to your network.
HOST_PORT 8787 The host port Docker publishes the node on. PUBLIC_ORIGIN must name it.
STUGA_VOLUME_NAME stuga_pgdata The Postgres volume.
COMPOSE_PROJECT_NAME stuga The Compose project. Give a second stack on the same host its own project, volume and port.
BACKUP_DIR ./backups Where backups go, the node's own and ./stuga's.
POSTGRES_PASSWORD stuga The database password. install.sh and ./stuga upgrade write a random one when .env has none.

Postgres keeps its own copy of POSTGRES_PASSWORD, so changing it in .env alone locks the node out. To change it, delete the line and run docker compose stop node, ./stuga db-password and docker compose up -d.

To add to the stack, for example another volume, put the change in compose.override.yml and set COMPOSE_FILE=compose.yml:compose.override.yml in .env, so that ./stuga uses the same files as docker compose. While the stack runs another release than compose.yml names (Roll back), COMPOSE_FILE also names compose.rollback.yml: leave it there.

Reaching the node from other devices

The node is published on every address of the machine, and only whoever has its setup code can claim it (Getting started). PUBLIC_ORIGIN is the address invite links carry; install.sh sets it to this machine's address on the network. Reserve that address on your router so it does not change. To keep the node to this machine, set HOST_BIND=127.0.0.1, or to your network, set it to this machine's network address. Network access explains what each way of reaching the node protects.

On a server

On a machine with a public address, such as a VPS, the default HOST_BIND puts the node's plain http port on the internet. The node takes no password there from outside its network (Passwords over plain http), but sessions and pages still cross in the clear. Set HOST_BIND=127.0.0.1 and open the node through an SSH tunnel from your computer:

ssh -L 8787:127.0.0.1:8787 <server>

Then open http://localhost:8787 to claim the node and sign in. On a public address the install script prints the setup link this way, and ./stuga reset-password prints the same tunnel with its link. To reach the node from anywhere without a tunnel, turn on remote access, or serve HTTPS as below. While remote access is on, ./stuga status says so when HOST_BIND still publishes the port on every address.

For HTTPS, put a reverse proxy in front of the node, as Network access describes, or let the node serve TLS itself: mount the certificate directory into the node container in compose.override.yml, set TLS_CERT_DIR to its path inside the container, and set PUBLIC_ORIGIN to the https address. The container's health check and ./stuga then reach the node over https.

Members and agents can have the node fetch a URL, such as an image to upload. The node refuses private and reserved addresses, but inside its container it doesn't know the server's own public address, and a connection from the server to itself passes the provider's firewall. Keep other services on the server to 127.0.0.1 and ::1, or deny Docker's networks their ports in the server's firewall (DOCKER-USER for published ones), for IPv4 and IPv6.

Operator commands

./stuga runs the node's own stuga-node commands in a one-off container of the image that created the node's container (for a restore, the backup's release), and stops and starts the node around the commands that need that. What each command does, and its exit codes, are described in Operations.

Command
./stuga status Version, the release that last served the data, schema, whether the node is serving or why it refuses the data, the last backup, the remote access connector's state, and the setup link while nobody has claimed the node.
./stuga backup Stops the node, takes a verified backup, and starts the node again.
./stuga verify <backup> Checks that a backup is whole and restorable here.
./stuga list Backups, and what restores kept beside the data.
./stuga restore [--yes] <backup> Pulls the images of the release that served the backup's data, verifies the backup while the node still serves, asks you to type its name, restores it, and starts the stack on that release.
./stuga upgrade [<version>] Fetches the newest release's files (or the named one's), pulls its images unless they are already on the machine, starts them and waits until the node serves. The node backs up before it upgrades.
./stuga reset-password <username> Prints a password reset link. The node must be running.
./stuga media-scan [--reclaim] [--empty-trash[=<days>]] [--grace-hours=<hours>] Reports or reclaims media no document uses. The node must be running.
./stuga db-password Gives the database a random password and writes it to .env, when .env has none. The node must be stopped.

A <backup> is a name in BACKUP_DIR, or a path inside it. To restore a backup taken elsewhere, copy its directory into backups/ first.

The commands wait for a starting node for as long as it shows progress. Two shell variables bound that wait: STUGA_READY_QUIET_SECONDS (default 600) and STUGA_READY_MAX_SECONDS (default 7200).

A restore keeps what it replaced. Once you are satisfied, remove both copies. ./stuga list names them:

docker compose exec postgres psql -U stuga -d postgres -c 'DROP DATABASE "stuga_replaced_<stamp>"'
sudo rm -rf data/node.replaced-<stamp>

Upgrade

Settings → This node → About names a newer version once the node knows of one (Operations).

Read the release's Upgrade notes first. Then:

./stuga upgrade            # the newest release
./stuga upgrade 1.2.3      # or a given one

It downloads that release's compose.yml, env.example and stuga over the ones you have (your .env is not touched), pulls the new images, starts them and waits until the node serves. The new version backs up the database before it changes anything, and answers with a page that says so meanwhile. ./stuga upgrade ends by printing the version change and the restore command that undoes it; if the new version does not serve, it prints the command for the node's log and the restore command for the backup that version took, or, when it stopped before that backup and changed nothing, stops it and keeps the stack on the previous release as Roll back describes. It refuses a release older than the one that last served the data, downloaded or put there by hand, with the node running or stopped: going back is a restore. Compare the new env.example with your .env for settings the release adds. When .env has no POSTGRES_PASSWORD, the upgrade stops the node and gives the database a random one first.

A NAS app store that updates the images does the same without ./stuga: the node backs up before it upgrades, whatever started it. A NAS app or an image set to an older release than the one that served the data refuses it and changes nothing.

Without a connection to the registry

A node that cannot reach ghcr.io upgrades from images carried to it. On a machine that is online, fetch the release's three files and save its three images for the node's architecture (linux/arm64 or linux/amd64):

V=1.2.3
for f in compose.yml env.example stuga; do
  curl -fsSLO "https://github.com/stuga-dev/stuga/releases/download/v$V/$f"
done
for image in stuga-node stuga-postgres stuga-remote; do
  docker pull --platform linux/arm64 "ghcr.io/stuga-dev/$image:$V"
done
docker save -o "stuga-$V-images.tar" "ghcr.io/stuga-dev/stuga-node:$V" \
  "ghcr.io/stuga-dev/stuga-postgres:$V" "ghcr.io/stuga-dev/stuga-remote:$V"

Carry the three files and the archive to the node. There, load the images, put the three files over the ones in the Stuga directory, and upgrade as usual. With a compose.yml naming a newer version than the one that served the data, ./stuga upgrade uses it as it is and downloads nothing:

docker load -i stuga-1.2.3-images.tar
chmod +x stuga
./stuga upgrade

The backup, the wait and the way back are the same as with a connection. Keep the previous version's images until you are satisfied with the new one: a roll back starts the stack on them, and docker image prune -a would remove them. A first install works the same way, with ./stuga db-password and docker compose up -d in place of ./stuga upgrade.

From Stuga 0.1

Stuga 0.1 releases were previews, and 0.2 starts with new data. On a node that runs 0.1, ./stuga upgrade puts 0.1's files back and changes nothing. To move:

  1. In Stuga 0.1, export each workspace: Settings → This workspace → General → Export workspace.
  2. In the Stuga directory, remove the stack and its database: docker compose down -v. The directory's data/ and backups/ are 0.1's; remove the directory once you no longer need them.
  3. Install the new release into a new directory, set it up, invite your colleagues, and import each archive with Create a workspace → Start with → Import (Workspace archive).

Roll back

Restore the backup the new version took before it upgraded, which ./stuga upgrade names and Settings → This node → Backups shows with its command:

./stuga restore <backup>

The stack comes back on the release whose data the backup holds: the node, Postgres and remote access, whose images the restore pulls before it stops anything. Keep compose.yml: it names the newer images, but an older release's compose.yml does not know the database password. The restore writes compose.rollback.yml, which pins all three images to the older release, and adds it to COMPOSE_FILE in .env, so docker compose up -d keeps the stack there too.

The next ./stuga upgrade takes compose.rollback.yml out of .env and deletes it before it starts the newer release.

A node started on an older release than the one that served its data, by hand, by a NAS app or by install.sh --version, refuses the data and changes nothing: its page and log say what to do, and ./stuga status says why. ./stuga upgrade <version>, with the version that served the data, puts that release back.

Uninstall

In the Stuga directory:

docker compose down

This removes the containers. Your data stays in data/, backups/ and the stuga_pgdata volume, and the stuga_remote volume stays too. To delete them and the images too, run docker compose down -v --rmi all and remove the directory. The node writes its files as root, so on Linux that needs sudo rm -rf.