No description
  • Shell 86.1%
  • Nix 13.9%
Find a file
Sergei Poljanski cc3405fe34
All checks were successful
Deploy storage stack / deploy (push) Successful in 20s
.env.example: describe Garage, not MinIO
The MinIO root account is gone and the public bucket's uploader was
missing.

Assisted-by: Claude:fable-5-2
2026-11-11 04:28:34 +04:00
.forgejo/workflows Remove MinIO 2026-11-11 03:55:18 +04:00
init Remove MinIO 2026-11-11 03:55:18 +04:00
.env.example .env.example: describe Garage, not MinIO 2026-11-11 04:28:34 +04:00
.gitignore Initial commit 2026-05-26 17:13:14 +03:00
docker-compose.yml Remove MinIO 2026-11-11 03:55:18 +04:00
flake.lock Initial commit 2026-05-26 17:13:14 +03:00
flake.nix Remove MinIO 2026-11-11 03:55:18 +04:00
garage.toml Add Garage beside MinIO 2026-11-11 03:41:03 +04:00
README.md Remove MinIO 2026-11-11 03:55:18 +04:00
SECRETS.md Remove MinIO 2026-11-11 03:55:18 +04:00

storage

Postgres + Garage (S3) for asxp.io and any future small services that need a database or object storage. Single-host, no clustering. Deploys via Forgejo Actions to the same box that runs the apps.

What's here

  • docker-compose.yml — postgres, its one-shot init container, and garage.
  • garage.toml — Garage's config (single node, no secrets in it).
  • init/postgres-init.sh — idempotent role/DB bootstrap.
  • init/garage-bootstrap.sh — idempotent buckets, per-app keys and grants; run on the host by the deploy, through docker exec.
  • .forgejo/workflows/deploy.yaml — pushes compose + .env to the prod host and runs docker compose up -d.

Networking

Two Docker networks:

  • storage — internal-only; apps join this as an external network to reach postgres:5432 and garage:3900.
  • traefik — existing reverse-proxy network. Only Garage joins it, to expose the S3 API at https://s3.asxp.io.

Garage replaced MinIO in November 2026 (MinIO was abandoned upstream and its images removed from the registries).

The public bucket

Garage answers no anonymous S3 request. public is marked as a website instead, and Traefik sends unsigned GET/HEAD of https://s3.asxp.io/public/<key> to Garage's web endpoint. Anyone who knows a key can read it; nobody can list the bucket. Uploading needs the uploader key (see SECRETS.md).

Looking inside

The image has no shell; use the CLI through docker exec:

ssh prod 'docker exec storage-garage /garage status'
ssh prod 'docker exec storage-garage /garage bucket list'
ssh prod 'docker exec storage-garage /garage key list'

Adding a new app

  1. Pick an app name, e.g. foo.
  2. Pick a DB password, S3 access key, S3 secret key for it (any random hex).
  3. Add Forgejo secrets FOO_DB_PASSWORD, FOO_S3_ACCESS_KEY, FOO_S3_SECRET_KEY to this repo and to the foo repo.
  4. Extend init/postgres-init.sh with ensure_role "foo" ... and ensure_db "foo" "foo".
  5. Extend init/garage-bootstrap.sh with ensure_bucket "foo-...", an ensure_key foo ... and a grant for it.
  6. Pass the new envs through in docker-compose.yml and the deploy workflow.
  7. Push.

State and backups

Three named Docker volumes are stateful:

  • pg_data (Postgres data dir) — /var/lib/docker/volumes/storage_pg_data/_data
  • garage_data (S3 object bytes) and garage_meta (Garage's database, with a snapshot every 6 h) — /var/lib/docker/volumes/storage_garage_*/_data

/opt/storage/secrets/garage_rpc_secret (Garage's node-to-node and CLI secret) is made on the host at the first deploy and is not in Forgejo.

Server-level backup covers all three. If you ever move off whole-server backup, the minimum is pg_dumpall for Postgres and rclone sync of each bucket.

The old storage_minio_data volume is still on the host from before the move; nothing uses it.

Secrets

See SECRETS.md for the full list of Forgejo secrets this repo needs. Apps that consume storage (asxpio, future) also need the per-app credentials added on their repo so they can connect.