266 self-hostable apps, each deployable with a single
stacker.yml. Deploy to your laptop, your own server, or the cloud with one command - database setup, health checks, secrets, and remote monitoring included.
Eevery entry here is a ready-to-run deployment:
Pick a directory, generate secrets, and stacker deploy. Same layout, same commands.
cd stacker-projects/umami # pick any project, for instance umami
./scripts/generate-secrets.sh # generate .env from .env.example
stacker deploy # β running locally in minutesexport HETZNER_API_TOKEN=your_token
stacker deploy --target cloud --force-rebuild- Quick Start
- Deploy in 30 Seconds
- Deployment Targets
- PIPE β Connect Apps Together
- Remote Monitoring Without SSH
- Secret Management
- Common Commands
- Customization
- Security Checklist
- Troubleshooting
- Project Catalog
- Resources
cd umami # pick any project directory
./scripts/generate-secrets.sh # create .env from .env.example
stacker deploy # deploy locallyThen open http://localhost:PORT (the port is defined in the project's
stacker.yml).
If the CLI is missing, see the installation guide: https://github.com/trydirect/stacker
Prerequisites (local): Docker, Docker Compose, and the Stacker CLI.
# 1. Pick a project
cd plausible
# 2. Generate secrets
./scripts/generate-secrets.sh
# 3. Deploy to cloud
stacker deploy --target cloud --force-rebuildBest for development and testing on your own machine.
stacker deployDeploy to a Linux server you control (Ubuntu 24.04+/Debian 12+, 2 GB+ RAM, 20 GB+ disk, key-based SSH, Docker installed).
stacker config setup server --ip <IP> --user root --key ~/.ssh/id_ed25519
stacker deploy --target server --force-rebuildWhat happens: Stacker uploads the config bundle, generates
docker-compose.yml from stacker.yml under .stacker/ on the server, runs
docker compose up -d, waits for health checks, then runs post-deploy hooks.
When the Stacker API is unreachable (DNS or auth issues), deploy manually:
scp .stacker/docker-compose.yml .env root@<IP>:/home/trydirect/project/
ssh root@<IP> "docker compose -f /home/trydirect/project/docker-compose.yml -p project up -d"Fully automated provisioning. Requires a provider account, an API token, and an SSH key registered with the provider.
export HETZNER_API_TOKEN=your_token_here
# First deploy β creates a new server (5-10 minutes)
stacker deploy --target cloud --force-new
stacker status # get the server IP, then point DNS at it
# Later deploys β reuse the same server (omit --force-new)
stacker deploy --target cloud --force-rebuildCloud settings live in stacker.yml:
deploy:
target: cloud
cloud:
provider: hetzner # or: digitalocean, aws, linode, vultr
region: fsn1 # Frankfurt (nbg1, hel1, ash, hil, sjc, sin, ...)
size: cpx22 # 2 vCPU / 4 GB RAM
ssh_key: ~/.ssh/id_ed25519
public_ports: # REQUIRED β without it only SSH (22) is open
- "80"
- "443"public_ports opens the provider firewall. Forgetting it is the most common
cause of "app unreachable after deploy." You can also add ports afterwards:
stacker cloud firewall add --public-ports 8080/tcpHetzner server sizes:
| Size | vCPU | RAM | Price | Best for |
|---|---|---|---|---|
cpx12 |
1 | 2 GB | ~11.49 EUR | Small apps, testing |
cx23 |
2 | 4 GB | ~5.49 EUR | Most apps |
cpx32 |
4 | 8 GB | ~35 EUR | Heavy / multiple apps |
cpx42 |
4 | 8 GB | ~50 EUR | AI stacks |
Enable the status panel in stacker.yml so the Stacker agent can serve logs and
health from remote servers directly through the CLI β no SSH required:
monitoring:
status_panel: true
healthcheck:
endpoint: /health
interval: 30sWith status_panel: true, the stacker agent commands read directly from your
remote deployment:
stacker agent health # service health at a glance
stacker agent logs app-name --lines 200 # tail logs remotely
stacker agent status # server + container statusThis is the recommended way to inspect running services. Reserve manual
ssh root@server access for cases the agent cannot cover.
Stacker supports multiple reverse proxy types for automatic HTTPS and domain routing.
Configure in stacker.yml:
proxy:
type: traefik # or: caddy, nginx-proxy-manager, nginx, none
auto_detect: false
domains:
- domain: app.example.com
ssl: off # off | auto | manual
upstream: app:3000| Type | Image | Config | Auto-HTTPS | Notes |
|---|---|---|---|---|
traefik |
traefik:latest | Docker labels | β (Let's Encrypt) | Default for cloud deploys |
caddy |
caddy:2-alpine | Caddyfile | β (automatic) | Simple config, zero-touch TLS |
nginx-proxy-manager |
jc21/nginx-proxy-manager | Web UI | β (Let's Encrypt) | GUI-based management |
nginx |
nginx:alpine | nginx.conf | β | Manual config |
none |
β | β | β | No proxy (direct port access) |
| Stack | Proxy | Server | Status |
|---|---|---|---|
| bookstack | traefik | 138.199.238.202 | β agent online |
| ntfy | caddy | 188.245.97.117 | β agent online |
| onetimesecret | caddy | 138.199.238.202 | β agent online |
When deploying to cloud, public_ports opens the provider firewall. Without it,
only SSH (22) is open β the #1 cause of "app unreachable after deploy":
deploy:
cloud:
public_ports:
- "80"
- "443"Fix without redeploying:
stacker cloud firewall add --public-ports 8080/tcpEach project ships a generator that fills .env from .env.example:
cd project-name
./scripts/generate-secrets.sh # run onceGenerated values by naming convention:
| Variable pattern | Method | Length | Used for |
|---|---|---|---|
*_PASSWORD |
openssl rand -hex 16 |
32 chars | DB / service passwords |
*_SECRET* |
openssl rand -hex 32 |
64 chars | App encryption keys |
*_TOKEN |
openssl rand -base64 32 |
43 chars | Auth tokens |
*_KEY |
openssl rand -hex 32 |
64 chars | Encryption keys |
*_BASE |
openssl rand -hex 64 |
128 chars | Framework keys |
Edit .env by hand for domains and app-specific values. Never commit .env
(the included .gitignore protects it).
Manage secrets locally or remotely via the Stacker CLI:
Local mode (reads/writes .env):
stacker secrets list # list all secrets in .env
stacker secrets validate # check all ${VAR} refs are set
stacker secrets set DB_PASSWORD=secret # set a value in .env
stacker secrets get DB_PASSWORD # read a valueRemote mode (Vault-backed, requires deployed project):
stacker secrets set KEY --scope service --service my-app --body "value"
stacker secrets push --service my-app --env production
stacker secrets list --scope service --service my-appRemote secrets are stored in Vault and layered on top of local .env values at
deploy time. Use for production credentials that shouldn't be in files.
Configuration
stacker config validate # check stacker.yml
stacker config show --resolved # show config with variables resolvedDeployment
stacker deploy # local
stacker deploy --target server --force-rebuild
stacker deploy --target cloud --force-rebuild
stacker deploy --dry-run # preview changes
stacker deploy --no-hooks # skip hooks (CI)Monitoring (works remotely with monitoring.status_panel: true)
stacker status # deployment status
stacker agent health # agent + service health
stacker agent logs app_name --lines 100PIPE commands
stacker pipe scan # discover container endpoints
stacker pipe create <src> <tgt> # create a data pipe
stacker pipe list # list pipe instances
stacker pipe activate <id> # start a pipe
stacker pipe deactivate <id> # pause a pipe
stacker pipe trigger <id> # one-shot execution
stacker pipe history <id> # view execution logStacker PIPE lets you connect applications on the same server so data flows automatically between them. Pipes are created at runtime via the CLI β no stacker.yml changes needed.
Basic workflow:
# 1. Deploy two apps on the same server (one at a time)
stacker deploy --target server --force-rebuild
# 2. Discover available endpoints from running containers
stacker pipe scan
# 3. Create a pipe between source and target
stacker pipe create <source-app> <target-app>
# 3b. Create a pipe with manual endpoints (no discovery)
stacker pipe create <source> <target> \
--source-endpoint "METHOD /path" \
--target-endpoint "METHOD /path" \
--source-fields field1,field2 \
--target-fields field1,field2 \
--name "my-pipe"
# 4. Activate the pipe (use --trigger manual for one-shot testing)
stacker pipe activate <pipe-id> --trigger manual
# 5. Trigger a one-shot test
stacker pipe trigger <pipe-id> --data '{"key":"value"}'Trigger types:
| Type | Behavior | Use Case |
|---|---|---|
| webhook | Fires instantly when data arrives | Real-time notifications |
| poll | Checks for new data every N seconds | Periodic syncs, batch jobs |
| manual | Only runs on explicit pipe trigger |
Testing, one-off transfers |
Built-in adapters: SMTP (target), IMAP/POP3 (source), WebhookBridge, HttpEndpoint, HtmlForm.
Example: Contact form β Telegram
stacker pipe scan --app website
stacker pipe create website telegram --name "contact-to-telegram"
stacker pipe activate <telegram-pipe-id>See the PIPE HOWTO at docs/pipe-howto.md for a complete walkthrough.
Edit the project's stacker.yml.
Switch database to PostgreSQL
services:
- name: db
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: "${DB_PASSWORD}"
POSTGRES_DB: myappChange the exposed port
app:
ports:
- "8080:8080" # host:containerAdd environment variables
app:
environment:
CUSTOM_VAR: "static_value"
DYNAMIC_VAR: "${FROM_ENV_FILE}"Add a Redis cache (bound to localhost)
services:
- name: redis
image: redis:7-alpine
ports:
- "127.0.0.1:6379:6379"
volumes:
- redis_data:/dataBefore deploying:
- Generated secrets with
./scripts/generate-secrets.sh - Updated domains and admin passwords in
.env - Confirmed
.envand.stacker/are never committed - Cloud only:
public_portslimited to the ports you actually need - Configured SSL/TLS and database backups
- Health checks enabled (included by default)
Never commit: .env, .env.local, .stacker/.
Container won't start
docker logs container_name
docker inspect container_name | grep -A 20 "Env"Port already in use
lsof -i :8080 # find the process, then kill it
# or change the port in stacker.ymlDatabase connection failed
docker compose ps # check the Status column
docker exec db_container pg_isreadyCan't reach the app
# Local: confirm the port is listening
netstat -tulpn | grep 8080
# Cloud: open the firewall port
stacker cloud firewall add --public-ports 8080/tcp
stacker cloud firewall listHigh memory usage β inspect with docker stats, then add limits:
services:
app:
mem_limit: 512m
memswap_limit: 1gEach project follows the same layout:
project-name/
.env.example # template (committed)
.env # secrets (gitignored)
stacker.yml # deployment config
scripts/generate-secrets.sh
Deployed and confirmed working on a clean Ubuntu 26.04 server via the Stacker server target. Start here for production.
| Project | Type | Port | Proxy | Image Fix / Notes |
|---|---|---|---|---|
| Agent Zero | AI agent | 50080 | β | autonomous agent; generated auth password |
| ai-mcp-app-builder | AI app builder | 3000 | β | describe an MCP app, get a live sandboxed instance; built from pinned upstream |
| Archon | AI assistant | 3000 | β | coleam00 stack; needs Claude/OpenAI key at first run |
| Bolt.diy | AI codegen | 5173 | β | stackblitz-labs; 9 LLM providers via env |
| Docling Serve | Doc AI | 5001 | β | IBM docβmarkdown/chunks API; API-key auth |
| Evolution API | WhatsApp API | 8080 | β | + postgres + redis; n8n-agency backend |
| Excaliflow | Diagrams | 3000 | β | Excalidraw-style diagrams with live animated flows |
| hn-briefing-agent | AI agent | 8000 | β | scheduled HN briefing API (Gemini/ADK); built from pinned upstream |
| kotaemon | RAG chat | 7860 | β | β open RCE #844 / IDOR #846 β self-host only, never expose publicly |
| Langchain-Chatchat | AI RAG | 8501 | β | stale 2024 images; FAISS + xinference |
| LightRAG | AI RAG | 9621 | β | + postgres + qdrant; auth on (admin login + API key, generated) |
| Local AI Packaged | AI stack | 8001 | caddy | full 28-service upstream stack; deployed (#1363) + baked; Quick Deploy in its README |
| Local ChatGPT | AI chat | 8501 | β | per-user memory chat (mem0 + qdrant + ollama), fully local |
| LocalAGI | AI agent | 8080 | β | + LocalAI (CPU) + localrecall postgres |
| Mautic | Marketing | 8080 | β | web+worker+cron + mysql; first-run wizard |
| Open Notebook | Notes | 8502 | β | + SurrealDB; default root:root replaced; API on 5055 |
| OpenAgent | AI knowledge | 14000 | β | ex-Casibase; + mysql |
| Paperless GPT | Doc AI | 8080 | β | + paperless-ngx + postgres + redis; LLM labels |
| RAG-as-a-Service | AI RAG | 8501 | β | streamlit RAG demo; API keys entered in UI |
| PipeShub | AI knowledge | 3000 | β | slim: mongo + redis + qdrant + neo4j |
| Scriberr | Transcription | 8080 | β | WhisperX; heavy first boot (model download) |
| Self-hosted AI Kit | AI stack | 5678 | β | official n8n starter: n8n + postgres + ollama + qdrant |
| Speaches | Speech API | 8000 | β | OpenAI-compatible STT/TTS; API-key auth |
| Strata | AI inference | 8080 | β | CUDA build from source; needs stacker gpus support (hand-written compose) |
| TradingAgents | Trading | β | β | multi-agent LLM trading framework (CLI); built from pinned upstream commit |
| Vane | AI search | 3000 | β | Perplexica successor; bundled SearXNG |
| WAHA | WhatsApp API | 3000 | β | WhatsApp HTTP API; sessions volume |
Contributions are welcome β one app per pull request keeps reviews fast.
Add a new app:
-
Create a directory named after the app (lowercase):
mkdir my-app && cd my-app -
Add the four standard files:
my-app/ stacker.yml # deployment config (required) .env.example # public config + empty secret placeholders (required) scripts/generate-secrets.sh # fills .env from .env.example (required) README.md # 3β5 lines: what it is, default port, any gotchas -
Validate before opening the PR:
stacker config validate ./scripts/generate-secrets.sh stacker deploy # confirm it comes up locally -
Add a row to the Project Catalog with the correct status badge (see legend below).
Versions: version: in stacker.yml is the stack's own version in semver,
MAJOR.MINOR.PATCH, starting at 1.0.0. It is not the app's image tag or an
upstream commit: Quick Deploy finds the stack's baked snapshot by this version.
Leave it out to always use the latest snapshot. CI refuses any other value.
Please do: pin image tags to a specific version (avoid :latest β see
Known image issues); bind database ports to 127.0.0.1;
keep real secrets out of the PR (only .env.example is committed).
Please don't: vendor the upstream app's full source tree β this repo ships
deployment configs, not application code. A stacker.yml that references the
official image is the whole point.
Maintain a stack: an app's upstream author can maintain its stack here. List
your GitHub login in stacker-projects/<app>/MAINTAINERS, one per line, and keep
sending pull requests from your fork; no write access is needed. CI names the
maintainers of every stack a pull request changes, so they are asked to review
it, and notes when the author changes only stacks they maintain. A change to a
MAINTAINERS file is reviewed like any other.
Status badges used in the catalog:
| Badge | Meaning |
|---|---|
| β | Tested & verified on a real server |
| π§ͺ | Configured, not yet re-deployed/verified |
| Works with a documented workaround (see notes) |
| Resource | Link |
|---|---|
| Stacker | https://github.com/trydirect/stacker |
| Stacker CLI docs | https://github.com/trydirect/stacker/blob/main/docs/ |
| awesome-selfhosted | https://github.com/awesome-selfhosted/awesome-selfhosted |
| Docker Hub | https://hub.docker.com |
| Hetzner Docs | https://docs.hetzner.cloud |
| Jitsi Docker guide | https://jitsi.github.io/handbook/docs/devops-guide/devops-guide-docker/ |
| Zitadel compose | https://zitadel.com/docs/self-hosting/deploy/compose |
Last updated: 2026-10-16 β 293 projects configured, 273 tested and verified on Ubuntu 26.04. Proxy support: traefik, caddy, nginx-proxy-manager.
β Star this repo if it saved you a weekend of Docker Compose wrangling.
Built with Stacker Β· Contributions welcome β one app per PR.

