Tangible ships as a single multi-arch (amd64/arm64) Docker image at
ghcr.io/bradbrownjr/tangible. The image bundles the FastAPI server and the
SvelteKit web SPA — the API and UI share one port.
Once the container is up, see the Admin guide for first-run setup, users, SSO, backups and upgrades. End-user docs live in the User guide. Every environment variable is in the Configuration reference.
Tags:
:latest— most recent stable release (default in compose examples):0.x.y/:0.x/:0— published fromvX.Y.Zgit tags:edge— built frommainon direct branch pushes (not from tag releases):sha-<short>— every commit
curl -fsSLO https://raw.githubusercontent.com/bradbrownjr/tangible/main/docker/docker-compose.standard.yml
docker compose -f docker-compose.standard.yml up -dThen browse to http://localhost:8000/ and register the first account —
it is automatically promoted to admin. To bootstrap headlessly instead,
set TANGIBLE_ADMIN_USERNAME and TANGIBLE_ADMIN_PASSWORD in your .env.
Pre-create the host paths:
mkdir -p /mnt/user/appdata/tangible/{data,config}Drop docker/docker-compose.unraid.yml into your project, set
TANGIBLE_PUBLIC_URL to your external URL (e.g. https://tangible.example.com), and
bring it up. The image runs as 99:100 (nobody:users) by default.
The container expects to be behind a reverse proxy; pair it with Caddy:
tangible.example.com {
reverse_proxy tangible:8000
}Make sure tangible and your Caddy container share an external Docker network
(TANGIBLE_NETWORK, default proxy).
Use the postgres.yml overlay alongside the standard file:
docker compose \
-f docker/docker-compose.standard.yml \
-f docker/docker-compose.postgres.yml up -dOr set TANGIBLE_DATABASE_URL=postgresql+psycopg://user:pass@host:5432/tangible
manually against an existing database.
The /data volume contains:
tangible.db(SQLite) and thetangible.db-wal/tangible.db-shmsidecarsphotos/<sha[0:2]>/<sha>— content-addressed photo files
The /config volume contains:
secret.key— session/cookie signing key (regenerate to invalidate sessions)tangible.yaml/tangible.env— optional config overrides
A logical export (no photos) is available at any time:
docker exec tangible tangible backup <username> - > tangible-backup.jsonRestore with:
docker exec -i tangible tangible restore <username> - < tangible-backup.jsonFor full backups, snapshot the /data and /config volumes together (e.g.
tar, restic, or your filesystem's snapshot facility). When using SQLite,
stop the container or use sqlite3 .backup to capture a consistent file.
Pull and recreate:
docker compose -f docker-compose.standard.yml pull
docker compose -f docker-compose.standard.yml up -dThe container runs alembic upgrade head on startup unless
TANGIBLE_DB_AUTO_MIGRATE=false.
GET /healthz— liveness/readiness probe (used by container HEALTHCHECK)GET /version— build metadata- Logs use structlog; set
TANGIBLE_LOG_FORMAT=jsonfor machine-readable output.
| Symptom | Cause / fix |
|---|---|
Permission denied on /data |
PUID/PGID mismatch with the host. Set PUID/PGID env vars to match your user. |
403 from /auth/login behind Caddy |
Set TANGIBLE_BEHIND_PROXY=true and TANGIBLE_ALLOWED_HOSTS=<domain>. |
| Cookies not persisting | If serving over HTTPS, ensure TANGIBLE_FORCE_HTTPS=true (default when PUBLIC_URL is https://). |
| Web UI 404s on refresh | The image's SPA fallback handles this; if you proxy to a subpath, use a top-level domain instead — Tangible does not yet support sub-path mounting. |