ChastityTrackerDeutsch
← Back to blog

2026-04-04 · 4 min read · TruBlue

Self-Hosting Chastity Tracker with Docker

If you'd rather not use the self-service portal, you can run Chastity Tracker entirely on your own server. The Docker image is publicly available — you keep full control over your data, your server, and your update schedule.

This guide is aimed at people with basic Linux and Docker knowledge. The software is provided as-is, without warranty of any kind.

Prerequisites

Before you start, make sure you have the following in place:

The GitHub repository is at github.com/trublue-2/chastitytracker. Release notes and known issues are documented there.

Docker Compose Setup

Create a directory for the installation and add a docker-compose.yml file:

mkdir chastitytracker && cd chastitytracker
services:
  chastitytracker:
    image: ghcr.io/trublue-2/chastitytracker:latest
    restart: unless-stopped
    ports:
      - "3000:3000"
    environment:
      NEXTAUTH_SECRET: "your-secret-here"
      NEXTAUTH_URL: "https://your-domain.com"
      DATABASE_URL: "file:./data/prod.db"
    volumes:
      - ./data:/app/data

The volume mount ./data:/app/data ensures the SQLite database and all uploaded photos survive container restarts and image updates. Without it, all data will be lost the next time you run docker compose pull.

Configuration (.env)

Environment variables can be set directly in the docker-compose.yml as shown above, or placed in a separate .env file in the same directory:

NEXTAUTH_SECRET=your-secret-here
NEXTAUTH_URL=https://your-domain.com
DATABASE_URL=file:./data/prod.db

NEXTAUTH_SECRET is required. Generate a random string of at least 32 characters, for example:

openssl rand -base64 32

NEXTAUTH_URL must match exactly the URL the app is served from — including the protocol (https://) and without a trailing slash.

DATABASE_URL points to the SQLite database file inside the container. The path file:./data/prod.db places the file inside the mounted volume.

Initial Admin Account

Without an admin user the app is not usable after startup. Set the following variables to have the admin account created automatically on first start, if no admin exists yet in the database:

INITIAL_ADMIN_USERNAME=admin
INITIAL_ADMIN_PASSWORD=your-strong-password
INITIAL_ADMIN_EMAIL=admin@your-domain.com

These variables are only evaluated on the very first start. They can be removed or left empty afterwards.

SMTP (optional, recommended)

Email notifications only work if SMTP is configured. Add the following variables:

SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=your-user@example.com
SMTP_PASS=your-password
SMTP_FROM=noreply@your-domain.com

Without SMTP, the app runs fine, but email-based features such as notifications and password reset will not be available.

AI Inspection Verification (optional)

The tracker can automatically recognise handwritten codes in inspection photos (Claude Vision). This requires an Anthropic API key:

ANTHROPIC_API_KEY=sk-ant-...

Without this key the app runs fully — automatic image analysis during inspections is simply disabled.

Push Notifications (optional)

Web push notifications (PWA) require VAPID keys. Generate them once with:

node -e "const c=require('crypto').createECDH('prime256v1');c.generateKeys();console.log(c.getPublicKey('base64url'));console.log(c.getPrivateKey('base64url'))"
VAPID_PUBLIC_KEY=generated-public-key
VAPID_PRIVATE_KEY=generated-private-key
VAPID_SUBJECT=mailto:admin@your-domain.com

Passkey / WebAuthn (optional)

For Face ID, Touch ID, or other passkeys to work in production, you need to set your domain:

WEBAUTHN_RP_ID=your-domain.com
WEBAUTHN_RP_ORIGIN=https://your-domain.com

These variables are not needed locally — without them the app falls back to localhost.

Starting the App

Start the container with:

docker compose up -d

On first start, the application initialises the database automatically and creates the admin account if INITIAL_ADMIN_* is set. The app will then be reachable at your domain.

Check the status with:

docker compose logs -f

Once you see Ready on http://0.0.0.0:3000 in the logs, the app is reachable.

Traefik / Reverse Proxy (optional)

If you use Traefik as a reverse proxy, you can add labels directly to the Compose file. Remove the ports block and replace it with labels:

services:
  chastitytracker:
    image: ghcr.io/trublue-2/chastitytracker:latest
    container_name: kg-yourname
    restart: unless-stopped
    environment:
      NEXTAUTH_SECRET: "your-secret-here"
      NEXTAUTH_URL: "https://yourname.your-domain.com"
      DATABASE_URL: "file:./data/prod.db"
    volumes:
      - ./data:/app/data
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.chastitytracker.rule=Host(`yourname.your-domain.com`)"
      - "traefik.http.routers.chastitytracker.entrypoints=websecure"
      - "traefik.http.routers.chastitytracker.tls.certresolver=letsencrypt"
      - "traefik.http.services.chastitytracker.loadbalancer.server.port=3000"
    networks:
      - traefik

networks:
  traefik:
    external: true

Replace yourname.your-domain.com and the network name with values that match your Traefik setup. TLS is handled automatically via Let's Encrypt, provided your Traefik configuration includes a letsencrypt certificate resolver.

Other reverse proxies (nginx, Caddy) work just as well — simply proxy port 3000 of the container and set NEXTAUTH_URL accordingly.

Updates

To update to a new image:

docker compose pull
docker compose up -d

Docker pulls the new image and restarts the container. The database in the volume is left untouched. Before each update, check the release notes in the GitHub repository for breaking changes or required migration steps.

Backing up the ./data directory before every update is strongly recommended.


If you run into issues, start with the GitHub Issues. If you'd rather not manage your own server, you can spin up a managed instance through the self-service portal.