Skip to content

Self-Hosting with Docker

TiniVault is designed to run on your own server. The easiest way to get started is with Docker Compose — no build tools or dependencies required.

Quick Start

Create a docker-compose.yml file and start TiniVault:

yaml
services:
  tinivault:
    image: ghcr.io/nhvu1988/tinivault:latest
    restart: unless-stopped
    ports:
      - "3000:3000"
    volumes:
      - ./data:/app/data
bash
docker compose up -d

That's it. All configuration has sensible defaults — you can add environment variables later to enable email, AI features, or HTTPS.

Open http://localhost:3000 in your browser and log in with the default credentials:

  • Username: admin
  • Password: admin

WARNING

Change the default password immediately after your first login.

Environment Variables

All environment variables are optional. TiniVault runs with sensible defaults out of the box. Add a .env file or environment: block in your docker-compose.yml to customise.

VariableDefaultDescription
PORT3000Port the server listens on
APP_URLhttp://localhost:<PORT>Public address of your instance (e.g. https://vault.example.com), used to build links in invitation and password-reset emails. Required once SMTP is configured — see Email below.
SECURE_COOKIESfalseSet to true if serving over HTTPS
TRIGGER_AT08:00Time of day to run triggers (HH:MM format)
RATE_LIMIT_RPM100Max requests per minute per IP
BETTER_AUTH_SECRETautoSigning secret for reset tokens and SSO state. Optional — if unset, a per-host secret is generated and persisted under DATA_DIR on first boot. Set it (a long random value) only to share one secret across multiple instances.
ADMIN_PASSPin the built-in admin password from the environment. When set, admin logs in with this value (a login-time override — the stored credential is never rewritten) and can't change the password in the app. Unset falls back to the database password (default admin, or one set in-app); removing it later restores in-app changeability.

Upgrade note: the auth system was rebuilt on better-auth. The first start after upgrading invalidates existing sessions, so everyone signs in once more — usernames and passwords are unchanged.

Email (SMTP)

Set these to enable Triggers & Automation, invitations, and password reset. SMTP_HOST and SMTP_FROM are required to activate email; SMTP_TO is the default recipient for test, trigger, and schedule emails. These SMTP settings — including the recipient — are also editable in Settings → Admin → Email without a restart; an env var, when set, takes precedence and locks the corresponding field.

APP_URL is required once SMTP is set. Invitation and password-reset emails embed an absolute link to your instance. For security, that link's address is taken only from APP_URL — it is never derived from the incoming request, which an attacker could spoof to redirect a reset link. If SMTP is configured without APP_URL, startup fails with a clear message. Set APP_URL to your public address, e.g. https://vault.example.com.

VariableDefaultDescription
SMTP_HOSTSMTP server address
SMTP_FROMSender email address
SMTP_TODefault recipient (trigger/schedule emails)
SMTP_PORT587SMTP port
SMTP_SECUREfalseUse TLS (true for port 465)
SMTP_USERSMTP username
SMTP_PASSSMTP password

AI Features

Set AI_BASE_URL and AI_MODEL to enable the AI chat assistant and photo analysis.

TiniVault uses the OpenAI-compatible API format, which means it works with any provider that exposes a /chat/completions endpoint following the OpenAI spec. Set AI_BASE_URL to the provider's base URL (without /chat/completions — TiniVault appends that automatically).

VariableDefaultDescription
AI_BASE_URLOpenAI-compatible API base URL (see examples below)
AI_MODELModel name for chat
AI_API_KEYAPI key for the AI provider
AI_VISION_MODELModel for photo analysis (defaults to AI_MODEL if not set)

Provider Examples

OpenAI

env
AI_BASE_URL=https://api.openai.com/v1
AI_MODEL=gpt-4o
AI_API_KEY=sk-...
AI_VISION_MODEL=gpt-4o

Google Gemini (via OpenAI-compatible endpoint)

env
AI_BASE_URL=https://generativelanguage.googleapis.com/v1beta/openai
AI_MODEL=gemini-2.5-flash
AI_API_KEY=AIza...
AI_VISION_MODEL=gemini-2.5-flash

OpenRouter (access to hundreds of models)

env
AI_BASE_URL=https://openrouter.ai/api/v1
AI_MODEL=anthropic/claude-sonnet-4-20250514
AI_API_KEY=sk-or-...
AI_VISION_MODEL=anthropic/claude-sonnet-4-20250514

Local models (via Ollama or LM Studio)

env
AI_BASE_URL=http://host.docker.internal:11434/v1
AI_MODEL=llama3
# No API key needed for local models

TIP

Any provider that supports the OpenAI /v1/chat/completions format will work. For photo analysis, the model set in AI_VISION_MODEL must support image inputs.

Single Sign-On (SSO)

Let people sign in with Google, GitHub, or any OpenID Connect provider (Authentik, Keycloak, Authelia, Zitadel, …). A provider's button appears only when its credentials are set. APP_URL must be set (SSO links and OAuth callbacks are built from it).

VariableDescription
GOOGLE_CLIENT_ID · GOOGLE_CLIENT_SECRETEnable Google sign-in
GITHUB_CLIENT_ID · GITHUB_CLIENT_SECRETEnable GitHub sign-in
OIDC_ISSUEROIDC issuer URL (discovery doc is read from <issuer>/.well-known/openid-configuration)
OIDC_CLIENT_ID · OIDC_CLIENT_SECRETOIDC client credentials
OIDC_PROVIDER_NAMEButton label (default SSO)
OIDC_PROVIDER_IDInternal id / callback path segment (default oidc)

Redirect / callback URLs to register with each provider (replace the host with your APP_URL):

Google / GitHub:  https://vault.example.com/api/auth/callback/google
                  https://vault.example.com/api/auth/callback/github
Generic OIDC:     https://vault.example.com/api/auth/oauth2/callback/oidc

How sign-in works (invite-only): SSO links to an existing account with the same email — it never creates one. So a user must first be invited (or already have a password account). The provider must report the email as verified, and the account's email must match. An uninvited or unverified email is rejected with a notice to ask an administrator. An invited user can sign in via SSO without ever setting a password.

Data Storage

All data is stored in the DATA_DIR directory:

data/
├── db.sqlite      # Database (items, tags, fields, settings, history)
├── files/         # Uploaded files (PDFs, documents, images)
└── thumbnails/    # Generated thumbnail images (WebP)

Mount this directory as a Docker volume to persist your data across container restarts.

Container user

Container runs as the bun user (UID/GID 1000). The host ./data directory must be owned by that UID. Override with --user <uid>:<gid> if needed.

Upgrading from versions before non-root

Older images ran as root, so ./data on the host is owned by root. Before pulling the new image, run once on the host:

bash
sudo chown -R 1000:1000 ./data

Skip this step and the container will fail to write the SQLite database on first start.

Backups

Use the built-in backup feature to download a .zip of your entire vault — database, files, and thumbnails included. Keep regular backups somewhere safe.

Updating

To update to the latest version:

bash
docker compose pull
docker compose up -d

Your data is safe — it lives in the mounted ./data volume, not inside the container.

Running Behind a Reverse Proxy

If you're running TiniVault behind Nginx, Caddy, or another reverse proxy with HTTPS:

  1. Set SECURE_COOKIES=true in your .env file
  2. Make sure your proxy forwards the X-Forwarded-For header (used for rate limiting)
  3. Point your proxy to http://localhost:3000

TiniVault · Family Asset Management · MIT License