Install with Docker Compose
One container runs the town hall and the town it serves, from the image ghcr.io/myrtillehq/engine. Its data lives on the volume myrtille-data: the database, the instance secret when you give none, the setup token and the town plan. Nothing external is needed. To install it in Coolify instead, see Install with Coolify.
What Myrtille never does here
Section titled “What Myrtille never does here”- It never listens beyond this machine by default: the port is published on
127.0.0.1only. - It never runs as root: the container runs as a user without rights, with a read-only file system except for its volume, no capability and
no-new-privileges. - It never writes a secret in its log lines. The setup token is printed once on the console.
Install
Section titled “Install”Save this file as docker-compose.yml in a directory of your choice. It is deploy/compose/docker-compose.yml in the repository.
# Myrtille with Docker Compose (S5-T04): one container, the town hall and the town it serves, its data on a volume.# docker compose -f deploy/compose/docker-compose.yml up -d# then open the address it listens on and follow the setup (the setup token is printed in `docker compose logs`).# Every setting is optional; deploy/compose/README.md says what each one does.name: myrtilleservices: myrtille: image: ${MYRTILLE_IMAGE:-ghcr.io/myrtillehq/engine:latest} restart: unless-stopped # The image runs as its user `node` and writes only /data (S4-T08): the rest of its file system is read-only. read_only: true tmpfs: - /tmp security_opt: - no-new-privileges:true cap_drop: - ALL environment: MYRTILLE_MODE: selfhost # The address people open (https turns on secure cookies). Empty: this machine only. MYRTILLE_PUBLIC_URL: ${MYRTILLE_PUBLIC_URL:-} # 32 random bytes (`openssl rand -hex 32`). Empty: generated at the first start and kept in the volume. MYRTILLE_SECRET: ${MYRTILLE_SECRET:-} # 1 behind one reverse proxy you control; 0 when the town hall is reached directly. MYRTILLE_TRUSTED_PROXIES: ${MYRTILLE_TRUSTED_PROXIES:-0} ports: # This machine only by default: put a reverse proxy with https in front to open it to others. - '${MYRTILLE_BIND:-127.0.0.1}:${MYRTILLE_PORT:-7420}:7420' volumes: - myrtille-data:/datavolumes: myrtille-data:From that directory:
docker compose up -ddocker compose logs myrtilleThe logs print the address and the setup token. Open the address: the first screen creates the owner account with that token, then the setup connects Coolify and shows the rest (First steps). By default the town hall listens on this machine only (127.0.0.1:7420).
Inside its container the town hall listens on every address, and the logs warn about it (CONFIG_INSECURE: it listens beyond this machine without an https MYRTILLE_PUBLIC_URL). With the default 127.0.0.1 port binding nobody else can reach it, so the warning only matters once you open it to others: then set an https MYRTILLE_PUBLIC_URL and it goes away.
Open it to others
Section titled “Open it to others”Put a reverse proxy with https in front of it, then set:
| Variable | What it does | Default |
|---|---|---|
MYRTILLE_PUBLIC_URL |
The address people open, for example https://town.example.com. With https, cookies are secure. |
empty: this machine only |
MYRTILLE_SECRET |
The instance secret, 32 random bytes (openssl rand -hex 32), which encrypts the stored credentials. |
empty: generated at the first start and kept in the volume |
MYRTILLE_TRUSTED_PROXIES |
How many reverse proxies you control sit in front of the town hall (1 behind one proxy). | 0 |
MYRTILLE_BIND, MYRTILLE_PORT |
The address and port of this machine the town hall is published on. | 127.0.0.1, 7420 |
MYRTILLE_IMAGE |
The image to run. | ghcr.io/myrtillehq/engine:latest |
Put them in a .env file next to docker-compose.yml, or in your environment. Giving MYRTILLE_SECRET yourself keeps the secret out of the volume, so a copy of the volume alone cannot read the credentials.
The town hall reads a few more variables that this file does not pass to the container: add them under environment: of the service if you need them. MYRTILLE_TELEMETRY=off sends no telemetry, whatever the owner answers (Telemetry). MYRTILLE_PUSH_CONTACT is the mailto: or https: contact the push services of the browsers may write to (Cloud features, self-hosted).
Update
Section titled “Update”Make a backup first (below), then:
docker compose pulldocker compose up -dThe town hall applies the migrations of its database when it starts. A town hall never opens a database migrated by a newer version: to go back, restore the backup made before the update.
Back up
Section titled “Back up”An owner downloads a hot copy of the database while the town hall runs, with the command line of Myrtille:
npx @myrtillehq/cli backup --town-hall https://town.example.com --out myrtille-backup.dbIt asks for the e-mail and password of an owner (or reads MYRTILLE_EMAIL and MYRTILLE_PASSWORD). It writes the file readable by you only, never over an existing file, and prints its size and SHA-256. Under the hood it calls POST /api/v1/backup, which an owner can also call directly.
The backup holds the accounts, the audit log and the state of the town plan. It also holds the credentials, encrypted with the instance secret, which the backup does not contain. Keep that secret apart: the value of MYRTILLE_SECRET, or the file myrtille.secret of the volume.
Restore
Section titled “Restore”The service stopped, the image restores a backup itself. It keeps the database it replaces beside it in the volume, never deletes it, and refuses a file that is not a backup of Myrtille:
docker compose stop myrtilledocker compose run --rm --no-deps -v "$PWD:/backup:ro" myrtille restore /backup/myrtille-backup.dbdocker compose start myrtilleThe container runs as its user node: the backup file and its directory must be readable by it (chmod a+r), or the restore says it cannot read it.
- While the service runs, the restore is refused.
- The backup is copied into the volume and checked there before anything else changes. If any step fails, the database in place stays as it was.
- At its next start the town hall records the restore in its audit log (
backup.restored). - A restore that a crash or a power loss interrupted is finished or undone by the next restore or the next start. Two restores never run at once.
- To go back to the database a restore replaced, restore the kept file itself, the service stopped:
docker compose run --rm --no-deps myrtille restore /data/town.db.before-restore-<time>.
Without the container, install the town hall beside the command line (npm install -g @myrtillehq/cli @myrtillehq/engine), then run myrtille backup --restore <file> --data-dir <dir> on the data directory of a stopped town hall on this machine.
Restore on a new machine
Section titled “Restore on a new machine”The backup cannot read its credentials without the instance secret of the town it came from. Before the first start on the new machine, set MYRTILLE_SECRET there to the secret of the old town: its old MYRTILLE_SECRET, or the content of the file myrtille.secret of its volume. On the old machine, the service stopped, the image prints it (it has no shell):
docker compose run --rm --no-deps --entrypoint node myrtille -e "process.stdout.write(require('fs').readFileSync('/data/myrtille.secret', 'utf8'))"Keep it as secret as the credentials it protects. MYRTILLE_SECRET comes before a secret file the new volume may already hold, so this works even if the service was started there once; keep it set for good. Then restore the backup as above and start the service. With another secret, the stored credentials cannot be read and the audit log no longer passes its check.