Deployment¶
Homestead runs as a Docker image. Three paths, increasing in operational fanciness:
- Unraid with the bundled Community Apps template.
- Plain docker-compose on any host.
- Kubernetes — DIY, but the image is just a stock Rails app, so any Rails-on-K8s recipe works.
Unraid¶
A community-template XML lives at
unraid/pantria.xml.
Walk-through, env-var reference and MySQL setup notes are in
unraid/README.md.
Short version:
- Provision a MySQL 8.4 container (Unraid CA has one).
- Drop the Homestead template into
templates-user/. - Fill in
APP_HOST+ DB creds +RAILS_MASTER_KEY. - Point a reverse proxy at the container — Homestead forces
httpsin production, so terminate TLS at the proxy (SWAG, NPM, Caddy, …).
The template defaults the Solid Queue worker to the same container as
the web process via bin/docker-entrypoint. If your receipt OCR load
gets heavy, split worker into its own container with
SOLID_QUEUE_DISABLE=1 set on the web container and the entrypoint
override rake solid_queue:start on the worker.
docker-compose¶
services:
db:
image: mysql:8.4
environment:
MYSQL_ROOT_PASSWORD: change-me
MYSQL_DATABASE: pantria
MYSQL_USER: pantria
MYSQL_PASSWORD: change-me-too
volumes:
- mysql_data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1"]
interval: 5s
web:
image: ghcr.io/sgraef/pantria:latest
environment:
RAILS_ENV: production
DATABASE_HOST: db
DATABASE_USERNAME: pantria
DATABASE_PASSWORD: change-me-too
DATABASE_NAME: pantria
RAILS_MASTER_KEY: <paste from config/master.key>
APP_HOST: pantria.your-domain.tld
# Optional SMTP for activation / password-reset emails
SMTP_ADDRESS: smtp.your-relay.tld
SMTP_PORT: "587"
SMTP_DOMAIN: your-domain.tld
# SMTP_USERNAME / SMTP_PASSWORD only if your relay needs auth
ports:
- "3000:3000"
depends_on:
db:
condition: service_healthy
volumes:
mysql_data:
SMTP¶
Notable behaviour: with SMTP_USERNAME and SMTP_PASSWORD both unset
Homestead connects to the relay unauthenticated. That's the right
behaviour for local postfix / Mailpit / internal smarthost relays
that don't need login. Set both to enable SMTP-AUTH. SMTP_DOMAIN
controls the HELO/EHLO greeting — set it to a domain you actually own
or strict relays (Mailgun, Postmark, Office 365) will reject your mail.
Upgrades¶
Pull the new image, then run the safe upgrade task, which takes a pre-upgrade backup before migrating so a failed migration is always recoverable:
docker compose pull web
docker compose run --rm web bundle exec rake homestead:upgrade
docker compose up -d web
homestead:upgrade writes a timestamped backup (database dump + Active
Storage blobs) under BACKUP_DIR (default /app/backups/<timestamp>/),
then runs pending migrations. If a migration fails it prints the exact
restore commands and leaves your data untouched in the backup.
Take a backup any time without upgrading:
Mount BACKUP_DIR to a volume outside /app/storage so backups persist
and don't recursively copy themselves. To roll back: restore
backups/<ts>/database.sql with mysql, copy backups/<ts>/active_storage
back over the Active Storage root, and redeploy the previous image tag.
Migrations also run automatically on plain boot via
bin/docker-entrypoint'srails db:prepare; preferhomestead:upgradefor the backup-first path.
Storage volumes¶
Two stateful directories the image expects:
| Path | Contents | When to back up |
|---|---|---|
MySQL /var/lib/mysql |
All structured data | Before every upgrade |
App /app/storage |
Active Storage uploads (receipts) | Before every upgrade + recurring schedule |
A small daily mysqldump --single-transaction pantria plus
tar zcf storage-$(date +%F).tgz /app/storage is plenty.
Health check¶
GET /up returns 200 OK once the database is reachable. Suitable for
reverse-proxy health checks and orchestrator liveness probes. Skips
host authorization (the homepage check fires before before_actions
run, so it works behind any proxy).
Image registry¶
CI publishes to GitHub Container Registry on every push to main:
Pin to a SHA for production; :latest is fine for personal use but
you'll auto-roll on every push.
CI gates¶
The GitHub Actions pipeline (.github/workflows/ci.yml) gates the
image build on:
- RuboCop (style)
- Sorbet (type-check)
- RSpec (unit / model / request specs against MySQL 8.4 service)
- Cypress (full e2e stack via docker-compose.test.yml)
RuboCop and Sorbet are continue-on-error: true — they surface
warnings in the run summary but don't block the build. RSpec and
Cypress are required.