Self-hosted file storage. Your personal Google Drive alternative.
Simple. Fast. Extensible.
- 📤 Upload, organize, and manage files with drag-and-drop
- 📦 Streaming uploads for large files (multi-GB support)
- 💾 Pluggable storage backends (local disk, S3, in-memory)
- 🔄 Content-addressed deduplication (saves storage space)
- 👥 Multi-user support with authentication and per-user quotas
- 📁 Virtual folder hierarchy with file organization
- 🗑️ Deleted items with configurable retention (per-user settings)
- 🎨 Tailwind CSS with responsive dark mode (system preference aware)
- 🔒 Secure by default (CSRF protection, bcrypt, rate limiting)
- 🔗 File sharing links with optional expiry and use limits
- 🔑 OIDC/SSO support (Authentik, Authelia, Keycloak, etc.)
- 🐳 Easy Docker deployment with multi-arch support
- 🗄️ PostgreSQL or SQLite database options
- 📊 Health checks and Prometheus metrics
Trove is for people who want straightforward self-hosted file storage without the overhead of a full cloud suite.
| Trove | Nextcloud | Seafile | |
|---|---|---|---|
| Setup complexity | Single Docker container | Multi-container, heavy config | Moderate |
| Image size | ~18 MB | ~1 GB+ | ~200 MB |
| Storage backends | Disk, S3/R2/B2, MinIO | Disk, S3 (plugin) | Disk, S3 |
| Tech stack | Go + SQLite/Postgres | PHP + MySQL | Python + MySQL |
| Focus | File storage only | Full office suite | File sync + sharing |
If you just want to store, organize, and share files — without a calendar, contacts app, or office suite — Trove is for you.
Prerequisites: Docker and Docker Compose
Create a docker-compose.yml:
services:
app:
image: ghcr.io/agjmills/trove:latest
restart: unless-stopped
env_file: .env
volumes:
- ./data:/app/data
- /tmp
depends_on:
postgres:
condition: service_healthy
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
- POSTGRES_DB=trove
- POSTGRES_USER=trove
- POSTGRES_PASSWORD=${DB_PASSWORD}
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U trove"]
interval: 10s
timeout: 5s
retries: 5
volumes:
postgres-data:Create a .env:
ENV=production
SESSION_SECRET= # openssl rand -base64 32
DB_PASSWORD= # choose a strong password
DEFAULT_USER_QUOTA=10G
MAX_UPLOAD_SIZE=500M
# Required when running behind a reverse proxy (Traefik, Nginx, Caddy, etc.)
# Set to your proxy's Docker network CIDR — check with: docker network inspect <network>
TRUSTED_PROXY_CIDRS=172.18.0.0/16Then:
docker compose up -dTrove will be available at http://localhost:8080. The first account you register becomes admin — go to /register to set it up.
Multi-arch images available for linux/amd64 and linux/arm64.
For reverse proxy setup (Traefik, Nginx, Caddy), OIDC, S3 storage, and troubleshooting, see INSTALL.md.
- Deploy and go to
/register— the first account becomes admin automatically - Set
ENABLE_REGISTRATION=falsein.envand restart to lock down signups - Add other users via the admin panel, or let them log in via OIDC
If you're using OIDC only with ENABLE_REGISTRATION=false, the first OIDC login on a fresh database auto-provisions an admin account — you won't get locked out.
The admin panel prevents you from switching your own account to OIDC while logged in, to stop you accidentally locking yourself out. Another admin can do it, or you can set the
identity_providercolumn directly in the database.
Trove supports multiple storage backends, configured via the STORAGE_BACKEND environment variable.
Stores files on the local filesystem with path traversal protection using Go 1.23+ os.Root.
STORAGE_BACKEND=disk
STORAGE_PATH=./data/filesStores files in S3 or any S3-compatible service (MinIO, Cloudflare R2, Backblaze B2, rustfs).
Uses native AWS SDK environment variables and credential chain:
| Variable | Description |
|---|---|
S3_BUCKET |
Bucket name (required) |
S3_USE_PATH_STYLE |
Set to true for MinIO/rustfs |
AWS_REGION |
AWS region |
AWS_ACCESS_KEY_ID |
Access key |
AWS_SECRET_ACCESS_KEY |
Secret key |
AWS_ENDPOINT_URL |
Custom endpoint for S3-compatible services |
The SDK also supports ~/.aws/credentials, ~/.aws/config, and IAM roles.
# AWS S3
STORAGE_BACKEND=s3
S3_BUCKET=my-trove-bucket
AWS_REGION=us-east-1
# S3-compatible (MinIO, rustfs)
STORAGE_BACKEND=s3
S3_BUCKET=my-trove-bucket
S3_USE_PATH_STYLE=true
AWS_ENDPOINT_URL=http://localhost:9000
AWS_ACCESS_KEY_ID=minioadmin
AWS_SECRET_ACCESS_KEY=minioadminTrove supports OIDC for single sign-on with Authentik, Authelia, Keycloak, or any other OIDC-compatible provider.
OIDC_ENABLED=true
OIDC_ISSUER_URL=https://auth.example.com/application/o/trove/
OIDC_CLIENT_ID=your-client-id
OIDC_CLIENT_SECRET=your-client-secret
OIDC_REDIRECT_URL=https://trove.example.com/auth/oidc/callbackNew users are auto-provisioned on first OIDC login. To migrate an existing local account to OIDC:
- In the admin panel go to Users and find the account
- Switch their identity provider from
InternaltoOIDC - They log in via OIDC — their subject gets linked and local password auth is disabled for that account
| Variable | Default | Description |
|---|---|---|
OIDC_ENABLED |
false |
Enable OIDC |
OIDC_ISSUER_URL |
Provider discovery URL (include trailing slash for Authentik) | |
OIDC_CLIENT_ID |
Client ID | |
OIDC_CLIENT_SECRET |
Client secret | |
OIDC_REDIRECT_URL |
Callback URL (https://your-trove/auth/oidc/callback) |
|
OIDC_SCOPES |
openid email profile |
Scopes to request |
OIDC_USERNAME_CLAIM |
preferred_username |
Claim to use as username |
OIDC_EMAIL_CLAIM |
email |
Claim to use as email |
OIDC_ADMIN_CLAIM |
Claim that controls admin status | |
OIDC_ADMIN_VALUE |
Value that grants admin (e.g. admins) |
OIDC_ADMIN_CLAIM handles string, array (Authentik/Keycloak groups), and boolean claim shapes automatically.
# Server
TROVE_PORT=8080
ENV=production # development or production
# Database
DB_TYPE=postgres # postgres or sqlite
DB_HOST=postgres
DB_NAME=trove
DB_USER=trove
DB_PASSWORD=secret
# Storage
STORAGE_BACKEND=disk # disk, s3, or memory
STORAGE_PATH=./data/files # for disk backend
TEMP_DIR=/tmp # temp directory for uploads
# S3 (if STORAGE_BACKEND=s3)
S3_BUCKET=trove
S3_USE_PATH_STYLE=false # true for MinIO/rustfs
# AWS_REGION=us-east-1
# AWS_ACCESS_KEY_ID=...
# AWS_SECRET_ACCESS_KEY=...
# AWS_ENDPOINT_URL=http://localhost:9000
# Limits
DEFAULT_USER_QUOTA=10G
MAX_UPLOAD_SIZE=500M
# Security
SESSION_SECRET=change-in-production # openssl rand -base64 32
CSRF_ENABLED=true
# Reverse proxy — set to your proxy's Docker network CIDR
# Production mode requires this when running behind a proxy
# Find it with: docker network inspect <network_name> | grep Subnet
TRUSTED_PROXY_CIDRS=172.18.0.0/16See .env.example for all options and INSTALL.md for detailed deployment guides including reverse proxy setup for Traefik, Nginx, and Caddy.
See CONTRIBUTING.md for local setup, architecture notes, and how to submit changes.
GET /health - Returns server health with database and storage checks
{
"status": "healthy",
"version": "1.0.0 (commit: abc123)",
"checks": {
"database": {"status": "healthy", "latency": "2.1ms"},
"storage": {"status": "healthy", "latency": "0.5ms"}
},
"uptime": "2h15m30s"
}GET /metrics - Prometheus-compatible metrics endpoint
Available metrics:
trove_http_requests_total- HTTP request counters by method, path, statustrove_http_request_duration_seconds- Request latency histogramstrove_http_requests_in_flight- Current concurrent requeststrove_storage_usage_bytes- Per-user storage consumptiontrove_files_total- File upload counterstrove_login_attempts_total- Authentication metrics
Security Note: The metrics endpoint is unauthenticated. Restrict access in production.
Production uses JSON format:
{"time":"2025-11-24T10:30:00Z","level":"INFO","msg":"http request","method":"POST","path":"/upload","status":200,"duration_ms":145}Development uses human-readable text format.
| Method | Endpoint | Description |
|---|---|---|
POST |
/upload |
Upload file (multipart/form-data) |
GET |
/download/{id} |
Download file |
POST |
/delete/{id} |
Delete file |
| Method | Endpoint | Description |
|---|---|---|
POST |
/folder/create |
Create folder |
POST |
/folder/delete/{name} |
Delete empty folder |
| Method | Endpoint | Description |
|---|---|---|
POST |
/files/{id}/share |
Create a share link |
POST |
/share/{token}/revoke |
Revoke a share link |
GET |
/s/{token} |
Download via share link (public) |
| Method | Endpoint | Description |
|---|---|---|
GET |
/health |
Health check |
GET |
/metrics |
Prometheus metrics |
Share links let you hand a direct download URL to anyone — no account required on their end.
Open any file's detail page and expand Create share link. You can optionally set:
- Expiry date — the link stops working after the end of that day (UTC)
- Max uses — the link stops working after N downloads
The share URL looks like https://your-trove/s/<token>. Copy it from the Sharing section with one click.
To revoke a link before it expires or runs out of uses, hit Revoke next to it. Revoking is instant and permanent.
Security notes:
- Tokens are 32-byte cryptographically random values — they cannot be guessed
- Expired, exhausted, and revoked links all return 404 — no information leakage
- Only the file owner can create or revoke share links for their files
- Use count is incremented atomically — concurrent requests cannot bypass a max-uses limit
For security-related documentation including CSRF protection details and migration notes, see SECURITY.md.
Contributions welcome! See CONTRIBUTING.md.
Completed:
- ✅ Authentication & multi-user support
- ✅ File management with streaming uploads
- ✅ Storage quotas & deduplication
- ✅ Multiple storage backends (disk, S3, memory)
- ✅ Virtual folder hierarchy
- ✅ CSRF protection & rate limiting
- ✅ Health checks & Prometheus metrics
- ✅ Structured logging
- ✅ Tailwind CSS with responsive dark mode
- ✅ Production-ready Docker images (~18MB)
- ✅ Deleted items with configurable retention
- ✅ OIDC/SSO authentication
- ✅ File sharing links
Planned:
- Version history
- Thumbnail generation
- Bulk operations
- REST API with authentication
Open source. See LICENSE file.