self-hosting guide

your gear data.
your hardware.

packry ships as a single Docker image with an embedded migration runner. bring your own postgres and you're up in under a minute.

docker
single image
postgres
only dependency
auto
migrations on start
before you start

prerequisites

docker
Docker Engine 20+ or Docker Desktop
postgresql 16
Bundled in compose, or bring your own
reverse proxy
Optional — Caddy, nginx, or Traefik for HTTPS
option a / recommended

docker compose

the easiest way to get started. this spins up both packry and a postgres database, with automatic health checks and persistent storage.

01

create your .env file

# .env — place next to docker-compose.yml
POSTGRES_PASSWORD=your-strong-password-here
BETTER_AUTH_SECRET=$(openssl rand -hex 32)
APP_URL=https://packry.example.com
RESEND_API_KEY=re_your_key_here
EMAIL_FROM="packry <noreply@yourdomain.com>"
02

create docker-compose.yml

services:
  db:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_DB: packry
      POSTGRES_USER: packry
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - "5433:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U packry -d packry"]
      interval: 10s
      timeout: 5s
      retries: 5

  app:
    image: ghcr.io/danielkerwin/packry:latest
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgres://packry:${POSTGRES_PASSWORD}@db:5432/packry
      BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
      APP_URL: ${APP_URL}
      RESEND_API_KEY: ${RESEND_API_KEY}
      EMAIL_FROM: ${EMAIL_FROM}

volumes:
  postgres_data:
03

start it up

docker compose up -d

packry will run migrations automatically on first boot, then start the server on port 3000.

or
option b / standalone

docker run

already have a postgres instance? run the packry container on its own and point it at your existing database.

docker run -d \
  --name packry \
  -p 3000:3000 \
  -e DATABASE_URL="postgres://packry:secret@host.docker.internal:5432/packry" \
  -e BETTER_AUTH_SECRET="$(openssl rand -hex 32)" \
  -e APP_URL="https://packry.example.com" \
  -e RESEND_API_KEY="re_your_key_here" \
  -e EMAIL_FROM="packry <noreply@yourdomain.com>" \
  --restart unless-stopped \
  ghcr.io/danielkerwin/packry:latest
configuration / environment

environment variables

all configuration is via environment variables. no config files to manage.

variabledescriptionrequired
DATABASE_URL
postgres://packry:secret@db:5432/packry
PostgreSQL connection string. When using docker-compose, this points to the db service internally. For standalone Docker, point to your existing Postgres instance.
Construct from your Postgres host, port, user, and password.
required
POSTGRES_PASSWORD
changeme
Password for the PostgreSQL database. Used by the postgres container to initialize the database and by the app to connect.
Choose a strong password. Used only if running the bundled Postgres container.
required
BETTER_AUTH_SECRET
a95ced22…(64 hex chars)
Secret key used to sign session tokens and encrypt auth data. Must be at least 32 bytes of randomness.
Run: openssl rand -hex 32
required
APP_URL
https://packry.example.com
Public URL where packry is served. Used for auth callbacks, invitation links, and email URLs.
Your domain or IP where packry will be served (include https://).
required
RESEND_API_KEY
re_xxxxxxxx_xxxxxxxxxxxxxxxxxxxx
API key for Resend, used to send transactional emails (magic link login, group invitations). Without this, email features are disabled.
Sign up at resend.com, create an API key, and verify your sending domain.
optional
EMAIL_FROM
"packry <noreply@yourdomain.com>"
The sender address for outgoing emails. Must match a verified domain in your Resend account.
Format: "display name <address@your-verified-domain>".
optional
good to know

operational notes

migrations
Database migrations run automatically every time the container starts. Safe to run repeatedly — already-applied migrations are skipped.
health check
The container exposes a health endpoint at /api/health. Docker will probe it every 30 seconds with a 10-second startup grace period.
updates
Pull the latest image and restart. Migrations handle schema changes automatically — no manual steps required.
reverse proxy
Packry runs on port 3000 over plain HTTP. For production use, place Caddy, nginx, or Traefik in front for TLS termination. For local or LAN use, you can access port 3000 directly. APP_URL should match how you access the app.
backups
Back up the postgres_data Docker volume or use pg_dump on a schedule. Packry itself is stateless — all data lives in Postgres.

prefer hosted?

skip the setup. create an account on the managed version and start building packs in seconds.

register free