| appdata | ||
| docs | ||
| scripts | ||
| stacks | ||
| .gitignore | ||
| HOMELAB-OVERVIEW.md | ||
| README.md | ||
Hermes Homelab Infrastructure
Professional Docker-based homelab infrastructure for media management, productivity services, monitoring, and gaming.
Table of Contents
- Overview
- System Specifications
- Quick Start
- Service Stacks
- Network Architecture
- Directory Structure
- Configuration Management
- Database Management
- Backup Strategy
- Maintenance
- Troubleshooting
Overview
This homelab runs 34+ containerized services across 7 Docker Compose stacks, providing:
- Media Management: Jellyfin, Sonarr, Radarr, Lidarr, Bazarr, Prowlarr, qBittorrent
- Productivity: Joplin, FreshRSS, Grocy, Linkwarden
- Communication: Synapse (Matrix), Element
- Gaming: Foundry VTT, RomM (ROM library manager)
- Monitoring: Grafana, Prometheus, Uptime Kuma, Netbox
- Infrastructure: Watchtower, Proton Bridge, Homepage dashboard
System Specifications
Hardware:
- CPU: Intel(R) Core(TM) i5-1235U (12th Gen) - 12 cores
- RAM: 31 GB
- Storage: 468 GB system + NAS mounts
- OS: Linux 6.8.0-88-generic
Current Resource Usage:
- Docker Containers: 34+ running
- Networks: 8 Docker networks (including shared-services)
- Volumes: 50+ persistent volumes
Quick Start
Prerequisites
- Docker and Docker Compose installed
- Git repository cloned to
/srv/ - External storage mounted at
/mnt/media-das/ - Network access on local subnet
Initial Setup
# 1. Navigate to services stack (must be started FIRST)
cd /srv/stacks/services
# 2. Create .env file from template
cp .env.template .env
nano .env # Fill in credentials
# 3. Start PostgreSQL and core services
docker compose up -d
# 4. Create databases for other stacks
docker exec -it postgres_hermes psql -U postgres
# Run database creation commands (see Database Management section)
# 5. Start other stacks in order
cd /srv/stacks/monitoring && docker compose up -d
cd /srv/stacks/apps && docker compose up -d
cd /srv/stacks/media && docker compose up -d
cd /srv/stacks/games-foundry && docker compose up -d
cd /srv/stacks/infra-updates && docker compose up -d
# 6. Verify all services are healthy
docker ps --format "table {{.Names}}\t{{.Status}}"
Service Stacks
1. Services Stack (Core Infrastructure)
Location: /srv/stacks/services/
Services:
- PostgreSQL (postgres_hermes) - Central database server
- Homepage (port 3333) - Dashboard
- FreshRSS (port 8070) - RSS reader
- Joplin Server (port 22300) - Note synchronization
- Grocy (port 9283) - Grocery management
- Linkwarden (port 3334) - Bookmark manager
- MeiliSearch (port 7700) - Search engine
Key Details:
- PostgreSQL password is shared across ALL stacks
- Must be started FIRST before other stacks
- Connected to
shared-servicesnetwork for cross-stack access
2. Monitoring Stack
Location: /srv/stacks/monitoring/
Services:
- Grafana (port 3000) - Metrics visualization
- Prometheus (port 9090) - Metrics collection
- node_exporter (port 9100) - System metrics
- Uptime Kuma (port 3001) - Uptime monitoring
- Netbox (port 8000) - IPAM/DCIM
- Redis (port 6379) - Cache for Netbox
Default Credentials:
- Grafana: admin / admin (change on first login)
- Netbox: admin / [NETBOX_SUPERUSER_PASSWORD from .env]
3. Apps Stack
Location: /srv/stacks/apps/
Services:
- Vikunja (port 3456) - Task management
- Synapse (port 8008) - Matrix homeserver
- Element (port 8009) - Matrix web client
- RomM (port 8080) - ROM library manager
- MariaDB (romm-db) - RomM database
Key Details:
- RomM uses MariaDB (not PostgreSQL)
- ROM library mounted at
/mnt/media-das/media/roms/ - Element configured to connect to Synapse
4. Media Stack
Location: /srv/stacks/media/
Services:
- Jellyfin (port 8096) - Media server
- Sonarr (port 8989) - TV show management
- Radarr (port 7878) - Movie management
- Lidarr (port 8686) - Music management
- Bazarr (port 6767) - Subtitle management
- Prowlarr (port 9696) - Indexer manager
- qBittorrent (port 8090) - Torrent client (VPN-enabled)
- NZBGet (port 6789) - Usenet client
- Jellyseerr (port 5055) - Media request management
- Kavita (port 5000) - Comic/manga reader
- AudioBookshelf (port 13378) - Audiobook server
Key Details:
- qBittorrent runs through VPN container
- Media stored on
/mnt/media-das/media/ - API keys required for interconnectivity
5. Games - Foundry VTT Stack
Location: /srv/stacks/games-foundry/
Services:
- Foundry VTT Main (port 30000)
- Foundry VTT Alt (port 30001)
Key Details:
- Requires Foundry VTT license
- Two instances for parallel campaigns
- Data stored in
/srv/appdata/foundry/
6. Infrastructure Updates Stack
Location: /srv/stacks/infra-updates/
Services:
- Watchtower (port 8080 API) - Automatic container updates
- Proton Bridge (port 25 SMTP, 143 IMAP) - Email bridge
Key Details:
- Watchtower updates containers on schedule (default: monthly)
- Excludes critical services from auto-update
- Sends email notifications via Proton Bridge
7. Remote Stack
Location: /srv/stacks/remote/
Services:
- Code Server (port 8443) - VS Code in browser
- Guacamole (port 8084) - Remote desktop gateway
Network Architecture
Docker Networks
shared-services (bridge)
├── postgres_hermes (services stack)
├── vikunja (apps stack)
├── synapse (apps stack)
├── grafana (monitoring stack)
├── netbox (monitoring stack)
└── uptimekuma (monitoring stack)
Default per-stack networks:
- services_default
- monitoring_default
- apps_default
- media_default
- games-foundry_default
- infra-updates_default
- remote_default
Port Mapping
| Service | Port | Stack | Access |
|---|---|---|---|
| Homepage | 3333 | services | Dashboard |
| Jellyfin | 8096 | media | Media streaming |
| Grafana | 3000 | monitoring | Metrics |
| Uptime Kuma | 3001 | monitoring | Uptime |
| Netbox | 8000 | monitoring | IPAM |
| Vikunja | 3456 | apps | Tasks |
| Synapse | 8008 | apps | Matrix |
| Element | 8009 | apps | Matrix client |
| RomM | 8080 | apps | ROM library |
| Foundry Main | 30000 | games | D&D VTT |
| Sonarr | 8989 | media | TV shows |
| Radarr | 7878 | media | Movies |
| qBittorrent | 8090 | media | Torrents |
| Jellyseerr | 5055 | media | Requests |
Directory Structure
/srv/
├── stacks/ # Docker Compose configurations
│ ├── services/ # Core services (PostgreSQL, Homepage, etc.)
│ ├── monitoring/ # Grafana, Prometheus, Netbox
│ ├── apps/ # Vikunja, Synapse, RomM
│ ├── media/ # Jellyfin, Sonarr, Radarr, etc.
│ ├── games-foundry/ # Foundry VTT instances
│ ├── infra-updates/ # Watchtower, Proton Bridge
│ └── remote/ # Code Server, Guacamole
│
├── appdata/ # Persistent application data
│ ├── services/ # Service stack data
│ ├── monitoring/ # Monitoring stack data
│ ├── apps/ # Apps stack data
│ ├── media/ # Media stack data
│ ├── foundry/ # Foundry VTT data
│ ├── infra-updates/ # Infrastructure data
│ └── remote/ # Remote access data
│
└── README.md # This file
/mnt/media-das/media/ # NAS-mounted storage
├── movies/ # Movie library
├── tv/ # TV show library
├── music/ # Music library
├── audiobooks/ # Audiobook library
├── comics/ # Comic/manga library
├── downloads/ # Download directory
└── roms/ # ROM library for RomM
├── TP/roms/ # Platform folders
├── nds/roms/
├── gameboy-color/roms/
├── mame/roms/
└── snes/roms/
Configuration Management
Environment Variables
Each stack has:
.envfile - Active configuration (gitignored, contains secrets).env.templatefile - Template for new deployments (tracked in Git)
Setup Process:
# Copy template
cp .env.template .env
# Edit with your values
nano .env
# Replace all CHANGE_ME values
Shared Credentials
PostgreSQL Root Password:
- Set in
/srv/stacks/services/.envasPG_ROOT_PASSWORD - Must be copied to monitoring and apps stacks
- Used by: Grafana, Netbox, Uptime Kuma, Vikunja, Synapse
API Key Generation: Most services generate API keys after first start:
- Start the service
- Access the web UI
- Navigate to Settings → General → Security
- Generate API key
- Add to
.envfile - Restart dependent services
Configuration Files
Some services require configuration files beyond environment variables:
| Service | Config File | Purpose |
|---|---|---|
| Vikunja | /srv/appdata/apps/vikunja/config.yml |
CORS, database settings |
| RomM | /srv/appdata/apps/romm/config.yml |
Database, auth settings |
| Netbox | /srv/appdata/monitoring/netbox/config/configuration.py |
Database, Redis, secret key |
| Prometheus | /srv/appdata/monitoring/prometheus/config/prometheus.yml |
Scrape targets |
| Homepage | /srv/appdata/services/homepage/config/*.yaml |
Dashboard configuration |
Database Management
PostgreSQL (postgres_hermes)
Connection Details:
- Host: postgres_hermes (internal) or localhost:5432 (external)
- User: postgres
- Password: [PG_ROOT_PASSWORD from services/.env]
Database Creation Commands:
-- Connect to PostgreSQL
docker exec -it postgres_hermes psql -U postgres
-- Services stack databases
CREATE DATABASE freshrss_db;
CREATE USER freshrss WITH PASSWORD '[FRESHRSS_DB_PASSWORD]';
GRANT ALL PRIVILEGES ON DATABASE freshrss_db TO freshrss;
CREATE DATABASE grocy_db;
CREATE USER grocy WITH PASSWORD '[GROCY_DB_PASSWORD]';
GRANT ALL PRIVILEGES ON DATABASE grocy_db TO grocy;
CREATE DATABASE joplin_db;
CREATE USER joplin WITH PASSWORD '[JOPLIN_DB_PASSWORD]';
GRANT ALL PRIVILEGES ON DATABASE joplin_db TO joplin;
CREATE DATABASE linkwarden_db;
CREATE USER linkwarden WITH PASSWORD '[LINKWARDEN_DB_PASSWORD]';
GRANT ALL PRIVILEGES ON DATABASE linkwarden_db TO linkwarden;
-- Monitoring stack databases
CREATE DATABASE grafana_db;
CREATE USER grafana WITH PASSWORD '[GRAFANA_DB_PASSWORD]';
GRANT ALL PRIVILEGES ON DATABASE grafana_db TO grafana;
CREATE DATABASE netbox_db;
CREATE USER netbox WITH PASSWORD '[NETBOX_DB_PASSWORD]';
GRANT ALL PRIVILEGES ON DATABASE netbox_db TO netbox;
CREATE DATABASE uptimekuma_db;
CREATE USER uptimekuma WITH PASSWORD '[UPTIMEKUMA_DB_PASSWORD]';
GRANT ALL PRIVILEGES ON DATABASE uptimekuma_db TO uptimekuma;
-- Apps stack databases
CREATE DATABASE vikunja_db;
CREATE USER vikunja WITH PASSWORD '[VIKUNJA_DB_PASSWORD]';
GRANT ALL PRIVILEGES ON DATABASE vikunja_db TO vikunja;
CREATE DATABASE synapse_db;
CREATE USER synapse WITH PASSWORD '[SYNAPSE_DB_PASSWORD]';
GRANT ALL PRIVILEGES ON DATABASE synapse_db TO synapse;
-- Grant schema permissions (PostgreSQL 15+ requirement)
\c vikunja_db
GRANT ALL ON SCHEMA public TO vikunja;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO vikunja;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO vikunja;
\c synapse_db
GRANT ALL ON SCHEMA public TO synapse;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO synapse;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO synapse;
\c grafana_db
GRANT ALL ON SCHEMA public TO grafana;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO grafana;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO grafana;
\c netbox_db
GRANT ALL ON SCHEMA public TO netbox;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO netbox;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO netbox;
\c uptimekuma_db
GRANT ALL ON SCHEMA public TO uptimekuma;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO uptimekuma;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO uptimekuma;
MariaDB (romm-db)
RomM v4 uses MariaDB instead of PostgreSQL. The database is automatically created by the container.
Connection Details:
- Host: romm-db
- Port: 3306
- Database: romm_db
- User: romm
- Password: [ROMM_DB_PASSWORD from apps/.env]
Backup Strategy
Database Backups
PostgreSQL:
# Backup all databases
docker exec postgres_hermes pg_dumpall -U postgres > /srv/backups/postgres_backup_$(date +%Y%m%d).sql
# Backup specific database
docker exec postgres_hermes pg_dump -U postgres -d vikunja_db > /srv/backups/vikunja_backup_$(date +%Y%m%d).sql
# Restore database
docker exec -i postgres_hermes psql -U postgres < /srv/backups/postgres_backup_20250101.sql
MariaDB (RomM):
# Backup RomM database
docker exec romm-db mysqldump -u romm -p'[ROMM_DB_PASSWORD]' romm_db > /srv/backups/romm_backup_$(date +%Y%m%d).sql
# Restore RomM database
docker exec -i romm-db mysql -u romm -p'[ROMM_DB_PASSWORD]' romm_db < /srv/backups/romm_backup_20250101.sql
Application Data Backups
# Backup all appdata
tar -czf /srv/backups/appdata_backup_$(date +%Y%m%d).tar.gz /srv/appdata/
# Backup specific stack
tar -czf /srv/backups/monitoring_backup_$(date +%Y%m%d).tar.gz /srv/appdata/monitoring/
# Restore appdata
tar -xzf /srv/backups/appdata_backup_20250101.tar.gz -C /
Configuration Backups
# Backup all compose configurations
tar -czf /srv/backups/stacks_backup_$(date +%Y%m%d).tar.gz /srv/stacks/ --exclude='*.env'
# Backup environment templates (safe to commit to Git)
cp /srv/stacks/*/.env.template /srv/backups/env-templates/
Maintenance
Regular Tasks
Daily:
- Monitor container health:
docker ps --filter "health=unhealthy" - Check disk space:
df -h - Review logs for errors:
docker logs [container] --since 24h
Weekly:
- Review Uptime Kuma for service outages
- Check Grafana dashboards for resource usage trends
- Review Watchtower logs for update failures
Monthly:
- Update containers manually (critical services)
- Backup databases
- Review and rotate logs
- Clean up unused Docker resources:
docker system prune -a
Service Updates
Automatic Updates (via Watchtower):
- Runs on schedule (default: monthly at 1 AM)
- Excludes critical services: jellyfin, joplin, meili_search, postgres_hermes, foundry-main, foundry-alt
Manual Updates (for critical services):
# 1. Backup database first
# 2. Pull new image
docker compose pull [service]
# 3. Recreate container
docker compose up -d [service]
# 4. Verify functionality
docker logs -f [service]
Log Management
View logs:
# Follow live logs
docker logs -f [container]
# View last 100 lines
docker logs --tail 100 [container]
# View logs since timestamp
docker logs --since 2024-01-01T00:00:00 [container]
Rotate logs:
# Configure in /etc/docker/daemon.json
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
Resource Monitoring
Check resource usage:
# Overall stats
docker stats
# Specific container
docker stats [container]
# System resources
htop
Troubleshooting
Common Issues
1. Container Unhealthy
Symptom: docker ps shows unhealthy status
Diagnosis:
# Check logs
docker logs [container]
# Inspect health check
docker inspect [container] | jq '.[0].State.Health'
# Test health endpoint manually
docker exec [container] curl -f http://localhost:[port]/health
Common Fixes:
- Verify database connectivity
- Check configuration file syntax
- Ensure proper permissions on mounted volumes
- Review healthcheck endpoint (use unauthenticated routes)
2. Database Connection Failed
Symptom: Service can't connect to PostgreSQL
Diagnosis:
# Check PostgreSQL is running
docker ps | grep postgres_hermes
# Check if container is on shared-services network
docker network inspect shared-services
# Test connection from container
docker exec [container] ping postgres_hermes
Fix:
# Connect container to shared-services network
docker network connect shared-services [container]
# Verify PostgreSQL password matches in .env files
3. Permission Denied Errors (PostgreSQL 15+)
Symptom: pq: permission denied for schema public
Fix:
docker exec -it postgres_hermes psql -U postgres -d [database]
GRANT ALL ON SCHEMA public TO [user];
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO [user];
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO [user];
4. RomM Not Scanning Library
Symptom: RomM shows "Directory not found" errors
Fix:
- Verify ROM folder structure:
/platform/roms/game/ - Check mount permissions:
ls -la /mnt/media-das/media/roms/ - Ensure config.yml is writable (644 permissions)
- Verify MariaDB is healthy:
docker logs romm-db
5. Watchtower Not Sending Emails
Symptom: No email notifications after updates
Diagnosis:
# Check Watchtower logs
docker logs watchtower
# Test Proton Bridge connectivity
docker exec watchtower nc -zv proton-bridge 25
Fix:
- Verify SMTP credentials in .env
- Ensure Proton Bridge is running
- Check both containers are on same network
Health Check Commands
# Check all container statuses
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
# Check network connectivity
docker network ls
docker network inspect shared-services
# Check volume mounts
docker inspect [container] | jq '.[0].Mounts'
# Check environment variables
docker exec [container] env | grep -i db
# Check disk space
df -h /srv/appdata/
df -h /mnt/media-das/
Service-Specific Troubleshooting
Vikunja:
- Check
/srv/appdata/apps/vikunja/config.ymlexists - Verify CORS settings include publicurl
- Ensure PostgreSQL connection on shared-services network
RomM:
- Verify MariaDB (not PostgreSQL) connection
- Check folder structure:
platform/roms/required - Ensure config.yml permissions: 644
Netbox:
- Verify Redis is running
- Check configuration.py exists
- Ensure SECRET_KEY is 50+ characters
- Healthcheck should use
/login/not/api/
Grafana:
- Default login: admin/admin
- Check database connection to postgres_hermes
- Verify network connectivity to Prometheus
Support and Documentation
Official Documentation:
- Each stack folder contains a detailed README.md
- Service-specific configurations in
/srv/appdata/*/README.md - Environment variable templates:
.env.templatefiles
Useful Commands:
# Quick health check
cd /srv && find stacks/ -name compose.yml -execdir docker compose ps \;
# Restart all services
cd /srv && find stacks/ -name compose.yml -execdir docker compose restart \;
# View all logs
cd /srv && find stacks/ -name compose.yml -execdir docker compose logs --tail=50 \;
SSL Certificates
SSL certificates for all homelab services are automatically managed by Traefik running on the edge device (Raspberry Pi) using Let's Encrypt with Cloudflare DNS challenge.
Current Domains
- claudiogoncalves.me (wildcard
*.claudiogoncalves.me) - local.claudiogoncalves.me (wildcard
*.local.claudiogoncalves.me) - whoami.claudiogoncalves.me
- foundryvtt.realidados.com
Certificate Management
- Certificate Authority: Let's Encrypt (ACME v2)
- Validation Method: DNS-01 challenge via Cloudflare API
- Certificate Lifetime: 90 days
- Auto-Renewal: Traefik automatically renews certificates 30 days before expiration
- Storage:
/srv/appdata/pi-core/traefik/acme.jsonon edge device
Backup & Recovery
- SSL certificates are backed up daily as part of the edge device restic backup
- In case of certificate loss, Traefik will automatically request new certificates on startup
- Backup location: Garage S3 at
s3://10.8.29.17:3900/restic-backup
Monitoring
- Check certificate status in Traefik dashboard:
https://traefik-dashboard.local.claudiogoncalves.me - View Traefik logs:
ssh rpi "docker logs traefik --tail 100" - Verify certificate expiration: Check browser certificate details or use
openssl s_client
Adding New Domains
- Update DNS records in Cloudflare to point to your public IP
- Add Traefik route in
/srv/stacks/pi-core/traefik/dynamic/[service].yml - Traefik will automatically request and configure SSL certificate
Last Updated: December 2025 Maintained By: Hermes Homelab Team Version: 1.0