244 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.
Unlike a typical awesome-selfhosted
list of links, every entry here is a ready-to-run deployment: pick a
directory, generate secrets, and stacker deploy. Same layout, same commands,
every app β from analytics and CMS to chat, password managers, and AI tools.
cd umami # pick any project
./scripts/generate-secrets.sh # generate .env from .env.example
stacker deploy # β running locally in minutesexport HETZNER_API_TOKEN=your_token
cd umami
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-rebuild --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 |
Stacker 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 (starts listening for triggers)
stacker pipe activate <pipe-id>
# 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.
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.
Each 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).
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 logDocker (local / on-server)
docker compose ps # list containers
docker compose logs -f app_name # live logs
docker compose restart db # restart a service
docker compose down # stop (add -v to also delete data)
docker exec -it app_name sh # shell into a containerEdit 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 | Image Fix / Notes |
|---|---|---|---|
| Activepieces | Automation | 8080 | Zapier alternative |
| Appsmith | Low-code | 80 | Internal tool builder |
| Aptabase | Analytics | 3000 | + postgres + clickhouse |
| Bitmagnet | BitTorrent | 3333 | + postgres |
| Bitwarden | Password mgr | 80 | vaultwarden + postgres |
| CyberChef | Data tools | 8000 | container listens on 8080 |
| Dashy | Dashboard | 8082 | container listens on 8080, not 80 |
| d8a | Analytics | 3000 | |
| Daily Stars Explorer | Astronomy | 8080 | |
| Directus | Headless CMS | 8055 | REST + GraphQL API |
| Discourse | Forum | 80 | needs pgvector/pgvector instead of plain postgres |
| Docmost | Wiki | 3000 | needs Redis service |
| Druid | Analytics | 8888 | pinned apache/druid:31.0.0; removed broken druid_extensions_loadList env var |
| FileBrowser | File manager | 8080 | |
| Floci | Local cloud | 4500 | TLS disabled; FLOCI_BASE_URL must match external IP |
| Ganymede | Video archive | 4000 | |
| Gitea | Git hosting | 3000 | |
| Gitness | Git hosting | 3000 | needs GITNESS_PRINCIPAL_ADMIN_EMAIL |
| Ghost | Blogging | 2368 | |
| Glances | System monitor | 61208 | |
| Gotify | Notifications | 8080 | |
| GoatCounter | Analytics | 8080 | single container, no secrets |
| Grafana | Monitoring | 3000 | admin/admin default |
| Grocy | Household | 9283 | groceries, chores, batteries |
| Grist | Spreadsheet | 8484 | relational spreadsheet with Python |
| HedgeDoc | Markdown editor | 3000 | |
| Home Assistant | Smart home | 8123 | slow startup (~50s) |
| Homer | Dashboard | 8080 | Static dashboard, no DB |
| Immich | Photos | 2283 | Google Photos alternative |
| IT-Tools | Developer tools | 8083 | container listens on 80, not 8080 |
| Jellyfin | Media server | 8096 | |
| Jitsi Meet | Video conf | 80/443 | uses :unstable tags; nginx permission bug |
| Kavita | Reading | 5000 | comics/manga reader |
| Komga | Comics/manga | 25600 | |
| Lemmy | Link aggregator | 1234 | pinned dessalines/lemmy:0.19.11 |
| Linkding | Bookmarks | 9090 | |
| Linkwarden | Bookmarks | 3000 | |
| Listmonk | Newsletter | 9000 | needs --install flag on first run |
| Mastodon | Social network | 3000 | |
| Maybe Finance | Personal finance | 3000 | |
| Mealie | Recipes | 9925 | SQLite by default |
| Meilisearch | Search | 7700 | |
| MeTube | Media download | 8081 | YouTube downloader |
| Memos | Notes | 5230 | |
| Metabase | BI | 3000 | |
| Mattermost | Team chat | 8065 | Slack alternative + postgres |
| Nextcloud | File sync | 8080 | |
| Navidrome | Music | 4533 | music streaming server |
| NocoDB | Database | 8080 | Airtable alternative + postgres |
| Offen | Analytics | 3000 | single container, privacy-first |
| Ombi | Media requests | 3579 | Plex/Jellyfin requests |
| Organizr | Dashboard | 9983 | HTPC dashboard |
| Open-WebUI | AI chat | 3000 | |
| Outline | Knowledge base | 3000 | |
| Paperless-ngx | Document mgmt | 8000 | |
| Pi-hole | DNS ad-block | 8080 | |
| Pingvin Share | File sharing | 3000 | WeTransfer alternative |
| Plausible | Analytics | 8000 | |
| Portainer | Docker mgmt | 9000 | container management UI |
| PostHog | Product analytics | 8000 | |
| Postiz App | Social scheduler | 4007 | 6 containers: app + postgres + redis + temporal + ES |
| Redash | BI | 5000 | |
| Rocket.Chat | Chat | 3000 | needs manual rs.initiate() on mongo |
| ROMM | ROM manager | 8080 | |
| RustFS | File storage | 3001 | |
| Rybbit | Analytics | 8080 | Clickhouse config bind mount |
| S4Core | File sharing | 8080 | |
| Semaphore | Ansible UI | 3000 | fixed DB_DIALECT typo |
| Statistics for Strava | Fitness | 8080 | waits for Strava API credentials |
| Strapi | CMS | 1337 | uses naskio/strapi instead of strapi |
| Supabase | Backend platform | 8000 | 10 containers; needs role passwords set via supabase_admin |
| Supabase-PostHog | Analytics + Backend | 8000 | Supabase + PostHog combined |
| Superset | BI | 8088 | needs manual superset fab create-admin + db upgrade + init |
| Synapse | Matrix chat | 8008 | |
| Syncthing | File sync | 8384 | P2P file synchronization |
| Tandoor | Recipes | 8080 | fixed port mapping 8080->80 (nginx) |
| Trilium | Notes | 8081 | |
| Umami | Analytics | 3000 | |
| UptimeKuma | Monitoring | 3001 | |
| Vaultwarden | Password mgr | 8080 | with Nginx Proxy Manager |
| Wallabag | Read-it-later | 80 | + postgres + redis |
| WordPress | CMS | 8080 | uses image: wordpress (no tag) + mysql:8.0 |
| Zitadel | IAM/SSO | 8080 | ExternalDomain must match server IP |
| Project | Type | Notes |
|---|---|---|
| ArchiveBox | Web archive | |
| AstrBot | AI chatbot | |
| ComfyUI | AI image gen | Cloud: container runs but not reachable |
| Coolify | PaaS | Cloud: local-exec provisioner error |
| Countly Server | Analytics | 4 containers running; nginx config needs manual SCP |
| Floci | File sharing | |
| GoAccess | Analytics | Config bind-mount files not copied to server |
| HitKeep | Bookmarks | Single container |
| Linkding | Bookmarks | Single container |
| Matomo | Analytics | App + MariaDB |
| Audiobookshelf | Audiobooks | Single container |
| Cal.com | Scheduling | Calendly alternative + postgres |
| Calibre-web | E-books | linuxserver/calibre-web |
| changedetection.io | Monitoring | Website change detection |
| Gotify | Notifications | Push notification server |
| Homer | Dashboard | Static dashboard, no DB |
| Kopia | Backup | Backup solution, no DB |
| Memos | Notes | Lightweight note-taking |
| Mealie | Recipes | Recipe manager |
| n8n | Automation | Workflow automation + postgres |
| One Time Secret | Secret sharing | Self-destructing secret links |
| Rallly | Scheduling | Date polls + postgres |
| Screego | Screen sharing | WebRTC screen sharing |
| Stirling-PDF | PDF tools | PDF manipulation, no DB |
| Typebot | Chatbots | Conversational form builder + postgres |
| Woodpecker CI | CI/CD | CI/CD pipeline engine |
| Audiobookshelf | Audiobooks | Single container |
| BookStack | Documentation | Wiki + MariaDB |
| Caddy | Web server | Auto-HTTPS reverse proxy |
| CrowdSec | Security | Threat detection |
| Duplicati | Backup | Encrypted backup to cloud/local |
| Frigate | Video NVR | AI object detection |
| FreshRSS | RSS reader | Self-hosted RSS |
| Home Assistant | Smart home | Home automation platform |
| Immich | Photos | Google Photos alternative |
| Keycloak | IAM/SSO | OAuth2/OIDC/SAML identity |
| Meilisearch | Search | Lightning-fast search engine |
| MinIO | Object store | S3-compatible storage |
| Speedtest Tracker | Monitoring | Internet speed tracking |
| Syncthing | File sync | P2P Dropbox alternative |
| Traefik | Reverse proxy | Auto-discovery proxy |
| WireGuard | VPN | Modern VPN |
| Chatwoot | Support | Customer support + REST API + webhooks |
| Directus | Headless CMS | REST + GraphQL API |
| Hanko | Auth | Passkeys + REST API |
| Infisical | Secrets | Secret management + REST API |
| Jellyseerr | Media requests | Media request management + REST API |
| Tautulli | Plex monitor | Plex analytics + REST API |
| Project | Issue | Workaround |
|---|---|---|
| discourse | discourse/base doesn't exist |
Use discourse/discourse:latest |
| jitsi/web | :latest tag doesn't exist |
Use jitsi/web:unstable (and prosody, jicofo, jvb) |
| strapi | strapi:latest doesn't exist |
Use naskio/strapi:latest |
| wordpress | wordpress:latest exists as wordpress |
Use wordpress (no tag) |
| druid | apache/druid:38.0.0 doesn't exist |
Use apache/druid:31.0.0 |
| lemmy | dessalines/lemmy:latest never existed |
Use dessalines/lemmy:0.19.11 |
| mysql:8-alpine | No such image | Use mysql:8.0 or postgres:16-alpine |
| Issue | Cause | Fix |
|---|---|---|
| Service labels stripped | Stacker doesn't pass services[].labels to compose |
Use app.env vars or nginx proxy instead |
| serde_yaml 0.9 quoting | Round-trip strips quotes, adds null/[] |
No fix yet; PR pending |
| Bind mount files not found | File paths resolve relative to compose location on remote | Use Dockerfile COPY instead of bind mounts |
:latest images not found |
Many projects have no latest tag |
Pin to specific version or tag |
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).
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.
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-08-19 β 244 projects configured, 83 tested and verified on Ubuntu 26.04.
β Star this repo if it saved you a weekend of Docker Compose wrangling.
Built with Stacker Β· Contributions welcome β one app per PR.

