Appearance
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/databash
docker compose up -dThat'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.
| Variable | Default | Description |
|---|---|---|
PORT | 3000 | Port the server listens on |
APP_URL | http://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_COOKIES | false | Set to true if serving over HTTPS |
TRIGGER_AT | 08:00 | Time of day to run triggers (HH:MM format) |
RATE_LIMIT_RPM | 100 | Max requests per minute per IP |
BETTER_AUTH_SECRET | auto | Signing 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_PASS | — | Pin 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_URLis 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 fromAPP_URL— it is never derived from the incoming request, which an attacker could spoof to redirect a reset link. If SMTP is configured withoutAPP_URL, startup fails with a clear message. SetAPP_URLto your public address, e.g.https://vault.example.com.
| Variable | Default | Description |
|---|---|---|
SMTP_HOST | — | SMTP server address |
SMTP_FROM | — | Sender email address |
SMTP_TO | — | Default recipient (trigger/schedule emails) |
SMTP_PORT | 587 | SMTP port |
SMTP_SECURE | false | Use TLS (true for port 465) |
SMTP_USER | — | SMTP username |
SMTP_PASS | — | SMTP 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).
| Variable | Default | Description |
|---|---|---|
AI_BASE_URL | — | OpenAI-compatible API base URL (see examples below) |
AI_MODEL | — | Model name for chat |
AI_API_KEY | — | API key for the AI provider |
AI_VISION_MODEL | — | Model 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-4oGoogle 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-flashOpenRouter (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-20250514Local 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 modelsTIP
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).
| Variable | Description |
|---|---|
GOOGLE_CLIENT_ID · GOOGLE_CLIENT_SECRET | Enable Google sign-in |
GITHUB_CLIENT_ID · GITHUB_CLIENT_SECRET | Enable GitHub sign-in |
OIDC_ISSUER | OIDC issuer URL (discovery doc is read from <issuer>/.well-known/openid-configuration) |
OIDC_CLIENT_ID · OIDC_CLIENT_SECRET | OIDC client credentials |
OIDC_PROVIDER_NAME | Button label (default SSO) |
OIDC_PROVIDER_ID | Internal 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/oidcHow 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 ./dataSkip 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 -dYour 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:
- Set
SECURE_COOKIES=truein your.envfile - Make sure your proxy forwards the
X-Forwarded-Forheader (used for rate limiting) - Point your proxy to
http://localhost:3000