Self-Hosting
Run the full Stash stack on your own infrastructure in under ten minutes. One Docker Compose file covers everything.
Prerequisites
1. Clone and configure
git clone https://github.com/Fergana-Labs/stash.git
cd stash
# Copy the env template and fill in your values
cp .env.example .envAt minimum set POSTGRES_USER, POSTGRES_PASSWORD, PUBLIC_URL (your domain), and CORS_ORIGINS. Embeddings default to local sentence-transformers — no API key required. Set OPENAI_API_KEY or HF_TOKEN to use a hosted embedding provider instead. The backend itself makes no LLM calls, so there are no other keys to configure.
Edit Caddyfile and replace app.example.com with your actual domain. Caddy handles TLS automatically via Let's Encrypt — no certificate management needed.
2. Start everything
docker compose -f docker-compose.prod.yml up -dThis starts four containers:
Alembic migrations run automatically on backend startup. Visit http://localhost:3457 to open the UI.
Environment variables
| Parameter | Type | Description |
|---|---|---|
| POSTGRES_USER / POSTGRES_PASSWORD / POSTGRES_DB* | string | Postgres credentials. Defaults are stash/stash/stash — change before going to production. |
| DATABASE_URL | string | Full PostgreSQL connection string. docker-compose.prod.yml auto-builds this from POSTGRES_* — only override if pointing at an external database. |
| PUBLIC_URL | string | Frontend origin. Used in invite links and CORS config. Default: http://localhost:3457 |
| CORS_ORIGINS | string | Comma-separated allowed origins. Default: http://localhost:3457,http://localhost:3456 |
| PORT | number | Backend port. Default: 3456 |
| DB_POOL_MIN / DB_POOL_MAX | number | Database connection pool size. Raise DB_POOL_MAX for high-traffic deployments. |
| EMBEDDING_PROVIDER | string | openai | huggingface | local | auto. Default: auto (detects from keys; falls back to local sentence-transformers if none set). |
| OPENAI_API_KEY | string | Optional. Enables the OpenAI-compatible embedding provider (also works with any OpenAI-compatible endpoint via EMBEDDING_API_URL). |
| HF_TOKEN | string | Optional. Enables the Hugging Face Inference API embedding provider. |
| EMBEDDING_MODEL | string | Override the embedding model name. Defaults depend on provider (text-embedding-3-small, BAAI/bge-small-en-v1.5, all-MiniLM-L6-v2). |
| S3_ENDPOINT | string | S3-compatible endpoint for file uploads (AWS, Cloudflare R2, MinIO). Leave blank to disable. |
| S3_BUCKET | string | S3 bucket name. |
| S3_ACCESS_KEY / S3_SECRET_KEY | string | S3 credentials. |
Optional: file storage
Stash uses any S3-compatible store for file uploads (images, PDFs, attachments). Set S3_ENDPOINT, S3_BUCKET, S3_ACCESS_KEY, and S3_SECRET_KEY in your .env. MinIO works well for fully local deployments:
# Add to docker-compose.yml services:
minio:
image: minio/minio
ports:
- "9000:9000"
- "9001:9001"
environment:
MINIO_ROOT_USER: stash
MINIO_ROOT_PASSWORD: stashdev
command: server /data --console-address ":9001"
volumes:
- minio_data:/dataThen in .env:
S3_ENDPOINT=http://localhost:9000
S3_BUCKET=stash
S3_ACCESS_KEY=stash
S3_SECRET_KEY=stashdev
S3_REGION=us-east-1Production checklist
Set POSTGRES_USER, POSTGRES_PASSWORD, and POSTGRES_DB in your .env before first run. Docker Compose and DATABASE_URL both pick them up automatically.
Set to your production frontend domain(s) only.
Set to your production frontend URL so invite links and share links resolve correctly.
Edit Caddyfile: replace app.example.com with your real domain. Caddy auto-provisions Let's Encrypt certificates on first start.
Raise to 50–100 for production load. Ensure your Postgres max_connections is higher.
For production, use a managed database (RDS, Supabase) with pgvector enabled. Remove the postgres service from docker-compose.prod.yml and set DATABASE_URL directly.
Upgrading
git pull
docker compose build
docker compose up -d
# Migrations run automatically on backend startupRunning tests
# Backend — requires a separate test database
docker compose up -d postgres
psql postgresql://stash:stash@localhost:5432/postgres -c "CREATE DATABASE stash_test;"
DATABASE_URL=postgresql://stash:stash@localhost:5432/stash_test \
python -m alembic upgrade head
DATABASE_URL=postgresql://stash:stash@localhost:5432/stash_test \
TEST_DATABASE_URL=postgresql://stash:stash@localhost:5432/stash_test \
python -m pytest backend/tests/ -v
# Frontend
cd frontend && npm test