A self-hosted home-lab stack of 11 services orchestrated by a single
manager.sh script. Targeted at a single Linux host (12 GB RAM, 100 GB
NVMe, optional object-storage mount) running behind a Traefik reverse
proxy.
| Directory | What |
|---|---|
databases/ |
PostgreSQL (pgvector), MySQL, Redis |
proxy/ |
Traefik v3 (reverse proxy) |
monitoring/ |
Grafana, Prometheus, Loki, Promtail, cAdvisor, node-exporter |
passwords/ |
Vaultwarden (password manager) |
containers/ |
Portainer CE (container management UI) |
cloud/ |
Seafile + memcached (file cloud) |
docs/ |
Paperless-ngx + Gotenberg + Tika (document management) |
automation/ |
n8n (automation) |
gallery/ |
Immich server + ML (photo gallery) |
ai-agent/ |
Hermes AI agent + dashboard |
dev-agent/ |
Developer workstation container: Flutter SDK + opencode CLI (in default profile) |
git clone https://github.com/dip-develop/dip-lab.git
cd dip-lab
# Create networks and data directories
./manager.sh setup
# Fill in secrets (one .env per service, see below)
cp databases/.env.example databases/.env
cp proxy/.env.example proxy/.env
# ...repeat for each service you intend to run
# Fix ownership
./manager.sh perm
# Start everything
./manager.sh start
./manager.sh statusAll services bind to 0.0.0.0 on the host; Traefik is the only one
exposed externally (80, 443).
| Service | Host port | Purpose |
|---|---|---|
| Passwords (Vaultwarden) | 8200 | Password manager |
| DockerUI (Portainer) | 9000 | Docker management UI |
| Cloud (Seafile) | 8383 | File hosting |
| Docs (Paperless) | 8000 | Document management |
| Automation (n8n) | 5678 | Automation |
| Gallery (Immich) | 2283 | Photo gallery |
| Hermes API / Dashboard | 18789 / 18790 | AI agent |
| Grafana / Prometheus | 3000 / 9090 | Monitoring |
| Traefik | 80, 443 | Reverse proxy (external) |
The dev-agent container exposes opencode serve on
127.0.0.1:4096 by default — see dev-agent/README.md.
Shared PostgreSQL, MySQL, and Redis for all services:
databases/init.sql— createsgallery,automation,docsdatabases, enablespgvectordatabases/mysql-init.sql— createsclouduser and grants on Seafile databases. Passwords are placeholders;databases/entrypoint.shsubstitutes real values fromdatabases/.envat container start.- PostgreSQL:
max_connections=200,shared_buffers=2GB - MySQL:
utf8mb4,max_connections=200 - Redis: password required,
maxmemory 512mb,allkeys-lru, RDB only
Inside the lab, services reach databases by hostname on the database
Docker network (postgres:5432, mysql:3306, redis:6379).
web— Traefik reverse proxy (80, 443 on host)internal— all app services, port-mapped to0.0.0.0:{port}.dev-agentis also oninternalso its opencode agents can reachautomation,hermes(the ai-agent service), andpostgresby hostname.database— PostgreSQL, MySQL, Redis (isolated)monitoring— Prometheus/Grafana/Loki/cAdvisor/node-exporter (defined inline, not pre-created)
Services that need DB access attach to both internal and database.
Others attach only to internal.
| Command | Description |
|---|---|
setup |
Create networks + data directories |
start [svc] |
Start all (db→proxy→rest) or one service |
stop [svc] |
Stop all (databases last) or one service |
restart [svc] |
Restart all or one service |
update [svc] |
docker compose pull then recreate containers |
build <svc> |
Build image for a service |
logs <svc> [--tail N] |
Follow logs for a service |
status |
docker compose ps for every enabled service |
exec <svc> <cmd> |
Run command inside a service container |
perm |
Fix file permissions / ownership |
profile <action> |
Manage disabled-service profiles |
backup [dir] |
Backup data directories to diplab-backup-<timestamp>.tar.gz |
restore <file> |
Restore data from backup archive |
clean |
Prune unused Docker volumes / networks |
stop-all |
docker stop every running container |
full-cleanup |
Nuke containers, images, volumes, networks |
-n <command> |
Dry-run (show what would happen) |
./manager.sh setup # create networks and folders
./manager.sh start # start enabled services
./manager.sh start dev-agent # start the developer workstation
./manager.sh logs gallery --tail 50
./manager.sh status
./manager.sh update
./manager.sh profile core # switch to core profile
./manager.sh backup # create backup archivesource completions.bashTwo mechanisms to exclude a service from bulk commands:
.disabled_services— list one service per line (gitignored; see.disabled_services.example).profilecommand —./manager.sh profile disable dev-agent,./manager.sh profile enable dev-agent, or./manager.sh profile <name>to switch profile.
Disabled services can still be targeted explicitly:
./manager.sh start dev-agent.
Built-in profiles (in .profiles/, listed in order of how much they
enable):
core— foundation only:databases+proxy+containers. No app services. Use for the very first start, or a host that only runs infrastructure.default— everyday stack:core+passwords+ai-agent+cloud+docs+gallery+dev-agent(9 services). Monitoring and automation are situational and are not included.media—default+monitoring+automation(Prometheus, Grafana, Loki, Promtail, cAdvisor, n8n). 9 services.dev—default+automation. Active development without the heavyweight media services (7 services).no-ai— everything exceptai-agentanddev-agent(9 services). Use on a host where you don't want any LLM gateway or agent runtime.full— everything enabled (11 services).
dev-agent is included in default, media, dev, and full. It
is excluded from core (no app services) and no-ai (per the
profile's intent). On a host with <8 GB RAM, disable it with
./manager.sh profile disable dev-agent.
Optional /mnt/object-storage/data mount. When present:
- Gallery (Immich) mounts upload, thumbs, profile, backups, library, encoded-video
- Docs (Paperless) mounts
/mnt/object-storage/data/docsas data root - Automation (n8n) mounts at
/mnt/object-storage - Cloud (Seafile) and automation have optional cross-service read-only mounts
When the mount is absent, manager.sh setup skips the object-storage
subdirectory creation. Docker will create empty root-owned dirs under
the bind paths instead, which is harmless but visible.
- Config:
proxy/traefik.yml+proxy/dynamic/security.yml - Dashboard password:
proxy/entrypoint.shhashes viahtpasswdat startup - ACME certs:
proxy/acme.json(gitignored) exposedByDefault: false— services opt in withtraefik.enable=truelabels- All services have Traefik labels commented out by default (direct port access pattern)
- All services accessible only via the internal Docker network except Traefik
- Traefik has direct internet access (for reverse proxy + ACME)
- TLS 1.2+ with secure cipher suites via
proxy/dynamic/security.yml - Strict-Transport-Security headers
- Rate limiting on Traefik (
ratelimitmiddleware, 100 req/s burst 50) - Secrets stay in
.env(gitignored) — every service has a<dir>/.env.exampletemplate - Strict permissions on sensitive data (
chmod 600on.envandacme.json) - Every service uses
security_opt: no-new-privileges:true+deploy.resources.limitsfor CPU/memory dev-agentruns with nodocker.sock, no--privileged, andopencodeagents are denied at the permission level from reading.envfiles, SSH keys, WireGuard configs, or pushing tomain
See SECURITY.md for the responsible-disclosure policy.
MIT. See LICENSE.
See CONTRIBUTING.md.