feat: Complete homelab GitOps setup with SSO and SSL

Core Infrastructure:
- Add LLDAP for centralized user authentication (lldap.fig.systems)
- Configure Tinyauth with LLDAP backend for SSO (auth.fig.systems)
- Set up Traefik v3.3 with Let's Encrypt SSL automation
- Create homelab Docker network for service isolation

Media Services:
- Configure Jellyfin with /media folder mappings (flix.fig.systems)
- Add Jellyseerr for media requests (requests.fig.systems)
- Update Immich with photo library access (photos.fig.systems)
- Set up Sonarr for TV automation (sonarr.fig.systems)
- Set up Radarr for movie automation (radarr.fig.systems)
- Configure SABnzbd for Usenet downloads (sabnzbd.fig.systems)
- Add qBittorrent for torrent downloads (qbt.fig.systems)

Utility Services:
- Update Linkwarden with proper networking (links.fig.systems)
- Configure Vikunja task management (tasks.fig.systems)
- Set up LubeLogger vehicle tracking (garage.fig.systems)
- Configure Calibre-web for ebooks (books.fig.systems)
- Add Booklore for book tracking (booklore.fig.systems)
- Update FreshRSS reader (rss.fig.systems)
- Update RSSHub with internal networking (rsshub.fig.systems)
- Update MicroBin pastebin (paste.fig.systems)
- Add File Browser for media access (files.fig.systems)

Technical Improvements:
- Standardize all compose files to compose.yaml (Docker best practice)
- Add Traefik labels to all services for SSL termination
- Implement proper network isolation (homelab + service-specific networks)
- Add health checks to database services
- Configure dual domain support (fig.systems + edfig.dev)
- Set proper /media folder mappings for all media services
- Add comprehensive README with deployment instructions

Security:
- Enable SSO via Tinyauth for most services
- Configure LLDAP with admin user (edfig/admin@edfig.dev)
- Services with built-in auth have SSO disabled by default
- All traffic secured with automatic Let's Encrypt certificates
This commit is contained in:
Claude 2025-11-05 19:12:04 +00:00
parent cae18a8dba
commit fd48fed9d8
No known key found for this signature in database
32 changed files with 921 additions and 306 deletions

219
README.md
View file

@ -1,3 +1,218 @@
# Homelab - Containers
# Homelab GitOps Configuration
This repo contains container related items for deployment in my homelab
This repository contains Docker Compose configurations for self-hosted home services.
## 🏗️ Infrastructure
### Core Services (Port 80/443)
- **Traefik** - Reverse proxy with automatic Let's Encrypt SSL
- **LLDAP** - Lightweight LDAP server for user management
- Admin: `edfig` (admin@edfig.dev)
- Web UI: https://lldap.fig.systems
- **Tinyauth** - SSO authentication via Traefik forward auth
- Connected to LLDAP for user authentication
- Web UI: https://auth.fig.systems
## 📁 Directory Structure
```
compose/
├── core/ # Infrastructure services
│ ├── traefik/ # Reverse proxy & SSL
│ ├── lldap/ # LDAP user directory
│ └── tinyauth/ # SSO authentication
├── media/ # Media services
│ ├── frontend/ # Media frontends
│ │ ├── jellyfin/ # Media server (flix.fig.systems)
│ │ ├── jellyseer/ # Request management (requests.fig.systems)
│ │ └── immich/ # Photo management (photos.fig.systems)
│ └── automation/ # Media automation
│ ├── sonarr/ # TV show management
│ ├── radarr/ # Movie management
│ ├── sabnzbd/ # Usenet downloader
│ └── qbittorrent/# Torrent client
└── services/ # Utility services
├── linkwarden/ # Bookmark manager (links.fig.systems)
├── vikunja/ # Task management (tasks.fig.systems)
├── lubelogger/ # Vehicle tracker (garage.fig.systems)
├── calibre-web/ # Ebook library (books.fig.systems)
├── booklore/ # Book tracking (booklore.fig.systems)
├── FreshRSS/ # RSS reader (rss.fig.systems)
├── rsshub/ # RSS feed generator (rsshub.fig.systems)
├── microbin/ # Pastebin (paste.fig.systems)
└── filebrowser/ # File manager (files.fig.systems)
```
## 🌐 Domains
All services are accessible via:
- Primary: `*.fig.systems`
- Secondary: `*.edfig.dev`
### Service URLs
| Service | URL | SSO Protected |
|---------|-----|---------------|
| Traefik Dashboard | traefik.fig.systems | ✅ |
| LLDAP | lldap.fig.systems | ✅ |
| Tinyauth | auth.fig.systems | ❌ |
| Jellyfin | flix.fig.systems | ❌* |
| Jellyseerr | requests.fig.systems | ✅ |
| Immich | photos.fig.systems | ❌* |
| Sonarr | sonarr.fig.systems | ✅ |
| Radarr | radarr.fig.systems | ✅ |
| SABnzbd | sabnzbd.fig.systems | ✅ |
| qBittorrent | qbt.fig.systems | ✅ |
| Linkwarden | links.fig.systems | ✅ |
| Vikunja | tasks.fig.systems | ✅ |
| LubeLogger | garage.fig.systems | ✅ |
| Calibre-web | books.fig.systems | ✅ |
| Booklore | booklore.fig.systems | ✅ |
| FreshRSS | rss.fig.systems | ✅ |
| RSSHub | rsshub.fig.systems | ❌* |
| MicroBin | paste.fig.systems | ❌* |
| File Browser | files.fig.systems | ✅ |
*Services marked with ❌* have their own authentication systems
## 📦 Media Folder Structure
The VM should have `/media` mounted at the root with this structure:
```
/media/
├── audiobooks/
├── books/
├── comics/
├── complete/ # Completed downloads
├── downloads/ # Active downloads
├── homemovies/
├── incomplete/ # Incomplete downloads
├── movies/
├── music/
├── photos/
└── tv/
```
## 🚀 Deployment
### Prerequisites
1. **DNS Configuration**: Point `*.fig.systems` and `*.edfig.dev` to your server IP
2. **Media Folders**: Ensure `/media` is mounted with the folder structure above
3. **Docker Network**: Create the homelab network
```bash
docker network create homelab
```
### Deployment Order
1. **Core Infrastructure** (must be first):
```bash
cd compose/core/traefik && docker compose up -d
cd compose/core/lldap && docker compose up -d
cd compose/core/tinyauth && docker compose up -d
```
2. **Configure LLDAP**:
- Visit https://lldap.fig.systems
- Login with admin credentials from `.env`
- Create an observer user for tinyauth
- Add regular users for authentication
3. **Update Passwords**:
- Update `LLDAP_LDAP_USER_PASS` in `core/lldap/.env`
- Update `LDAP_BIND_PASSWORD` in `core/tinyauth/.env` to match
- Update `SESSION_SECRET` in `core/tinyauth/.env`
- Update database passwords in service `.env` files
4. **Deploy Services**:
```bash
# Media frontend
cd compose/media/frontend/jellyfin && docker compose up -d
cd compose/media/frontend/jellyseer && docker compose up -d
cd compose/media/frontend/immich && docker compose up -d
# Media automation
cd compose/media/automation/sonarr && docker compose up -d
cd compose/media/automation/radarr && docker compose up -d
cd compose/media/automation/sabnzbd && docker compose up -d
cd compose/media/automation/qbittorrent && docker compose up -d
# Utility services
cd compose/services/linkwarden && docker compose up -d
cd compose/services/vikunja && docker compose up -d
cd compose/services/lubelogger && docker compose up -d
cd compose/services/calibre-web && docker compose up -d
cd compose/services/booklore && docker compose up -d
cd compose/services/FreshRSS && docker compose up -d
cd compose/services/rsshub && docker compose up -d
cd compose/services/microbin && docker compose up -d
cd compose/services/filebrowser && docker compose up -d
```
## 🔐 Security Considerations
1. **Change Default Passwords**: All `.env` files contain placeholder passwords marked with `changeme_*`
2. **LLDAP Observer User**: Create a readonly user in LLDAP for tinyauth to bind
3. **SSL Certificates**: Traefik automatically obtains Let's Encrypt certificates
4. **Network Isolation**: Services use internal networks for database/cache communication
5. **SSO**: Most services are protected by tinyauth forward authentication
## 📝 Configuration Files
Each service has its own `.env` file where applicable. Key files to review:
- `core/lldap/.env` - LDAP configuration and admin credentials
- `core/tinyauth/.env` - LDAP connection and session settings
- `media/frontend/immich/.env` - Photo management configuration
- `services/linkwarden/.env` - Bookmark manager settings
- `services/microbin/.env` - Pastebin configuration
## 🔧 Maintenance
### Viewing Logs
```bash
cd compose/[category]/[service]
docker compose logs -f
```
### Updating Services
```bash
cd compose/[category]/[service]
docker compose pull
docker compose up -d
```
### Backing Up Data
Important data locations:
- LLDAP: `compose/core/lldap/data/`
- Service configs: `compose/*/*/config/`
- Databases: `compose/*/*/db/` or `compose/*/*/pgdata/`
- Media: `/media/` (handle separately)
## 🐛 Troubleshooting
### Service won't start
1. Check logs: `docker compose logs`
2. Verify network exists: `docker network ls | grep homelab`
3. Check port conflicts: `docker ps -a`
### SSL certificate issues
1. Verify DNS points to your server
2. Check Traefik logs: `cd compose/core/traefik && docker compose logs`
3. Ensure ports 80 and 443 are open
### SSO not working
1. Verify tinyauth is running: `docker ps | grep tinyauth`
2. Check LLDAP connection in tinyauth logs
3. Verify LDAP bind credentials match in both services
## 📄 License
This is a personal homelab configuration. Use at your own risk.
## 🤝 Contributing
This is a personal repository, but feel free to use it as a reference for your own homelab!

25
compose/core/lldap/.env Normal file
View file

@ -0,0 +1,25 @@
# LLDAP Configuration
# Base DN for the LDAP directory
LLDAP_LDAP_BASE_DN=dc=fig,dc=systems
# Admin user configuration
LLDAP_LDAP_USER_DN=admin
LLDAP_LDAP_USER_EMAIL=admin@edfig.dev
LLDAP_LDAP_USER_PASS=changeme_please_set_secure_password
# JWT secret for session management (change this!)
LLDAP_JWT_SECRET=changeme_please_set_random_secret
# Database URL (SQLite by default)
LLDAP_DATABASE_URL=sqlite:///data/users.db
# Timezone
TZ=America/New_York
# Optional: SMTP configuration for password reset emails
# LLDAP_SMTP_OPTIONS__SERVER=smtp.gmail.com
# LLDAP_SMTP_OPTIONS__PORT=587
# LLDAP_SMTP_OPTIONS__SMTP_ENCRYPTION=STARTTLS
# LLDAP_SMTP_OPTIONS__USER=your-email@gmail.com
# LLDAP_SMTP_OPTIONS__PASSWORD=your-app-password
# LLDAP_SMTP_OPTIONS__FROM=LLDAP Admin <admin@edfig.dev>

View file

@ -0,0 +1,24 @@
services:
lldap:
container_name: lldap
image: lldap/lldap:stable
ports:
- "3890:3890" # LDAP
- "17170:17170" # Web UI
env_file: .env
volumes:
- ./data:/data
restart: unless-stopped
networks:
- homelab
labels:
traefik.enable: true
traefik.http.routers.lldap.rule: Host(`lldap.fig.systems`) || Host(`lldap.edfig.dev`)
traefik.http.routers.lldap.entrypoints: websecure
traefik.http.routers.lldap.tls.certresolver: letsencrypt
traefik.http.services.lldap.loadbalancer.server.port: 17170
traefik.http.routers.lldap.middlewares: tinyauth
networks:
homelab:
external: true

View file

@ -1,7 +1,27 @@
TZ=America/Los_Angeles
PUID=1000
PGID=1000
# Tinyauth Configuration
MYSQL_DATABASE=librenms
MYSQL_USER=librenms
MYSQL_PASSWORD=asupersecretpassword
# App URL - publicly accessible URL
APP_URL=https://auth.fig.systems
# Timezone
TZ=America/Los_Angeles
# LDAP Configuration - Connect to LLDAP
LDAP_ADDRESS=ldap://lldap:3890
LDAP_BASE_DN=dc=fig,dc=systems
LDAP_BIND_DN=uid=admin,ou=people,dc=fig,dc=systems
LDAP_BIND_PASSWORD=changeme_please_set_secure_password
LDAP_SEARCH_FILTER=(uid=%s)
LDAP_INSECURE=true
# Optional: Local users (if you want fallback auth)
# Format: username:bcrypt_hash:totp_secret (totp_secret is optional)
# Generate hash with: docker run --rm -it ghcr.io/steveiliop56/tinyauth:latest hash <password>
# USERS=
# Session configuration
SESSION_SECRET=changeme_please_set_random_session_secret
SESSION_MAX_AGE=86400
# Database (optional, uses SQLite by default at /data/tinyauth.db)
# DATABASE_URL=sqlite:///data/tinyauth.db

View file

@ -1,54 +1,26 @@
services:
traefik:
container_name: traefik
image: traefik:v3.3
command: --api.insecure=true --providers.docker
ports:
- 80:80
volumes:
- /var/run/docker.sock:/var/run/docker.sock
whoami:
container_name: whoami
image: traefik/whoami:latest
labels:
traefik.enable: true
traefik.http.routers.whoami.rule: Host(`whoami.example.com`)
traefik.http.routers.whoami.middlewares: tinyauth
tinyauth-frontend:
container_name: tinyauth-frontend
build:
context: .
dockerfile: frontend/Dockerfile.dev
volumes:
- ./frontend/src:/frontend/src
ports:
- 5173:5173
labels:
traefik.enable: true
traefik.http.routers.tinyauth.rule: Host(`tinyauth.example.com`)
tinyauth-backend:
container_name: tinyauth-backend
build:
context: .
dockerfile: Dockerfile.dev
args:
- VERSION=development
- COMMIT_HASH=development
- BUILD_TIMESTAMP=000-00-00T00:00:00Z
tinyauth:
container_name: tinyauth
image: ghcr.io/steveiliop56/tinyauth:latest
env_file: .env
volumes:
- ./internal:/tinyauth/internal
- ./cmd:/tinyauth/cmd
- ./main.go:/tinyauth/main.go
- /var/run/docker.sock:/var/run/docker.sock
- ./data:/data
ports:
- 3000:3000
- 4000:4000
restart: unless-stopped
networks:
- homelab
labels:
traefik.enable: true
traefik.http.middlewares.tinyauth.forwardauth.address: http://tinyauth-backend:3000/api/auth/traefik
# Web UI routing
traefik.http.routers.tinyauth.rule: Host(`auth.fig.systems`) || Host(`auth.edfig.dev`)
traefik.http.routers.tinyauth.entrypoints: websecure
traefik.http.routers.tinyauth.tls.certresolver: letsencrypt
traefik.http.routers.tinyauth.service: tinyauth-ui
traefik.http.services.tinyauth-ui.loadbalancer.server.port: 3000
# Forward Auth Middleware
traefik.http.middlewares.tinyauth.forwardauth.address: http://tinyauth:3000/api/auth/traefik
traefik.http.middlewares.tinyauth.forwardauth.trustforwardheader: true
traefik.http.middlewares.tinyauth.forwardauth.authresponseheaders: X-Forwarded-User
networks:
homelab:
external: true

View file

@ -0,0 +1,46 @@
services:
traefik:
container_name: traefik
image: traefik:v3.3
command:
# API Settings
- --api.dashboard=true
# Provider Settings
- --providers.docker=true
- --providers.docker.exposedbydefault=false
- --providers.docker.network=homelab
# Entrypoints
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
# HTTP to HTTPS redirect
- --entrypoints.web.http.redirections.entrypoint.to=websecure
- --entrypoints.web.http.redirections.entrypoint.scheme=https
# Let's Encrypt Certificate Resolver
- --certificatesresolvers.letsencrypt.acme.email=admin@edfig.dev
- --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
- --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web
# Logging
- --log.level=INFO
- --accesslog=true
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt:/letsencrypt
restart: unless-stopped
networks:
- homelab
labels:
traefik.enable: true
# Dashboard routing
traefik.http.routers.traefik.rule: Host(`traefik.fig.systems`) || Host(`traefik.edfig.dev`)
traefik.http.routers.traefik.entrypoints: websecure
traefik.http.routers.traefik.tls.certresolver: letsencrypt
traefik.http.routers.traefik.service: api@internal
traefik.http.routers.traefik.middlewares: tinyauth
networks:
homelab:
name: homelab
driver: bridge

View file

@ -0,0 +1,34 @@
# qBittorrent - Bittorrent client with WebUI
# Docs: https://docs.linuxserver.io/images/docker-qbittorrent/
services:
qbittorrent:
container_name: qbittorrent
image: lscr.io/linuxserver/qbittorrent:latest
environment:
- PUID=1000
- PGID=1000
- TZ=America/Los_Angeles
- WEBUI_PORT=8080
volumes:
- ./config:/config
- /media/downloads:/downloads
- /media/complete:/complete
- /media/incomplete:/incomplete
ports:
- "6881:6881" # BitTorrent port
- "6881:6881/udp" # BitTorrent DHT
restart: unless-stopped
networks:
- homelab
labels:
traefik.enable: true
traefik.http.routers.qbittorrent.rule: Host(`qbt.fig.systems`) || Host(`qbt.edfig.dev`)
traefik.http.routers.qbittorrent.entrypoints: websecure
traefik.http.routers.qbittorrent.tls.certresolver: letsencrypt
traefik.http.services.qbittorrent.loadbalancer.server.port: 8080
traefik.http.routers.qbittorrent.middlewares: tinyauth
networks:
homelab:
external: true

View file

@ -0,0 +1,33 @@
# Radarr - Movie Management and Automation
# Docs: https://wiki.servarr.com/radarr
services:
radarr:
container_name: radarr
image: lscr.io/linuxserver/radarr:latest
environment:
- PUID=1000
- PGID=1000
- TZ=America/Los_Angeles
volumes:
- ./config:/config
# Media library
- /media/movies:/media/movies
# Download folders
- /media/downloads:/downloads
- /media/complete:/complete
- /media/incomplete:/incomplete
restart: unless-stopped
networks:
- homelab
labels:
traefik.enable: true
traefik.http.routers.radarr.rule: Host(`radarr.fig.systems`) || Host(`radarr.edfig.dev`)
traefik.http.routers.radarr.entrypoints: websecure
traefik.http.routers.radarr.tls.certresolver: letsencrypt
traefik.http.services.radarr.loadbalancer.server.port: 7878
traefik.http.routers.radarr.middlewares: tinyauth
networks:
homelab:
external: true

View file

@ -1,11 +0,0 @@
services:
radarr:
image:
container_name: radarr
restart: unless-stopped
# ports:
# - ":"
# volumes:
# - ./data:/data
# environment:
# - VARIABLE=value

View file

@ -0,0 +1,30 @@
# SABnzbd - Usenet binary newsreader
# Docs: https://docs.linuxserver.io/images/docker-sabnzbd/
services:
sabnzbd:
container_name: sabnzbd
image: lscr.io/linuxserver/sabnzbd:latest
environment:
- PUID=1000
- PGID=1000
- TZ=America/Los_Angeles
volumes:
- ./config:/config
- /media/downloads:/downloads
- /media/complete:/complete
- /media/incomplete:/incomplete
restart: unless-stopped
networks:
- homelab
labels:
traefik.enable: true
traefik.http.routers.sabnzbd.rule: Host(`sabnzbd.fig.systems`) || Host(`sabnzbd.edfig.dev`)
traefik.http.routers.sabnzbd.entrypoints: websecure
traefik.http.routers.sabnzbd.tls.certresolver: letsencrypt
traefik.http.services.sabnzbd.loadbalancer.server.port: 8080
traefik.http.routers.sabnzbd.middlewares: tinyauth
networks:
homelab:
external: true

View file

@ -1,11 +0,0 @@
services:
sabnzbd:
image:
container_name: sabnzbd
restart: unless-stopped
# ports:
# - ":"
# volumes:
# - ./data:/data
# environment:
# - VARIABLE=value

View file

@ -0,0 +1,33 @@
# Sonarr - TV Show Management and Automation
# Docs: https://wiki.servarr.com/sonarr
services:
sonarr:
container_name: sonarr
image: lscr.io/linuxserver/sonarr:latest
environment:
- PUID=1000
- PGID=1000
- TZ=America/Los_Angeles
volumes:
- ./config:/config
# Media library
- /media/tv:/media/tv
# Download folders
- /media/downloads:/downloads
- /media/complete:/complete
- /media/incomplete:/incomplete
restart: unless-stopped
networks:
- homelab
labels:
traefik.enable: true
traefik.http.routers.sonarr.rule: Host(`sonarr.fig.systems`) || Host(`sonarr.edfig.dev`)
traefik.http.routers.sonarr.entrypoints: websecure
traefik.http.routers.sonarr.tls.certresolver: letsencrypt
traefik.http.services.sonarr.loadbalancer.server.port: 8989
traefik.http.routers.sonarr.middlewares: tinyauth
networks:
homelab:
external: true

View file

@ -1,11 +0,0 @@
services:
sonarr:
image:
container_name: sonarr
restart: unless-stopped
# ports:
# - ":"
# volumes:
# - ./data:/data
# environment:
# - VARIABLE=value

View file

@ -1,4 +1,5 @@
#
# Immich - Self-hosted photo and video management
# WARNING: To install Immich, follow our guide: https://docs.immich.app/install/docker-compose
#
# Make sure to use the docker-compose.yml of the current release:
@ -21,6 +22,8 @@ services:
# Do not edit the next line. If you want to change the media storage location on your system, edit the value of UPLOAD_LOCATION in the .env file
- ${UPLOAD_LOCATION}:/data
- /etc/localtime:/etc/localtime:ro
# External photo library (optional - can be imported into Immich)
- /media/photos:/media/photos:ro
env_file:
- .env
ports:
@ -29,8 +32,20 @@ services:
- redis
- database
restart: always
networks:
- homelab
- immich_internal
healthcheck:
disable: false
labels:
traefik.enable: true
traefik.docker.network: homelab
traefik.http.routers.immich.rule: Host(`photos.fig.systems`) || Host(`photos.edfig.dev`)
traefik.http.routers.immich.entrypoints: websecure
traefik.http.routers.immich.tls.certresolver: letsencrypt
traefik.http.services.immich.loadbalancer.server.port: 2283
# Optional: Enable SSO (note: Immich has its own user management)
# traefik.http.routers.immich.middlewares: tinyauth
immich-machine-learning:
container_name: immich_machine_learning
@ -45,6 +60,8 @@ services:
env_file:
- .env
restart: always
networks:
- immich_internal
healthcheck:
disable: false
@ -54,6 +71,8 @@ services:
healthcheck:
test: redis-cli ping || exit 1
restart: always
networks:
- immich_internal
database:
container_name: immich_postgres
@ -70,6 +89,15 @@ services:
- ${DB_DATA_LOCATION}:/var/lib/postgresql/data
shm_size: 128mb
restart: always
networks:
- immich_internal
networks:
homelab:
external: true
immich_internal:
name: immich_internal
driver: bridge
volumes:
model-cache:

View file

@ -1,11 +0,0 @@
services:
immich:
image:
container_name: immich
restart: unless-stopped
# ports:
# - ":"
# volumes:
# - ./data:/data
# environment:
# - VARIABLE=value

View file

@ -1,42 +1,50 @@
# Compose Docs:
# https://jellyfin.org/docs/general/installation/container/?method=docker-compose
# Transcoding Docs
# https://jellyfin.org/docs/general/post-install/transcoding/hardware-acceleration/nvidia
# Jellyfin Media Server
# Docs: https://jellyfin.org/docs/general/installation/container/?method=docker-compose
# Transcoding: https://jellyfin.org/docs/general/post-install/transcoding/hardware-acceleration/nvidia
services:
jellyfin:
image: jellyfin/jellyfin
container_name: jellyfin
user: uid:gid
ports:
- 8096:8096/tcp
- 7359:7359/udp
volumes:
- /path/to/config:/config
- /path/to/cache:/cache
- type: bind
source: /path/to/media
target: /media
- type: bind
source: /path/to/media2
target: /media2
read_only: true
# Optional - extra fonts to be used during transcoding with subtitle burn-in
- type: bind
source: /path/to/fonts
target: /usr/local/share/fonts/custom
read_only: true
restart: 'unless-stopped'
# Optional - alternative address used for autodiscovery
image: lscr.io/linuxserver/jellyfin:latest
environment:
- JELLYFIN_PublishedServerUrl=http://example.com
# Optional - may be necessary for docker healthcheck to pass if running in host network mode
extra_hosts:
- 'host.docker.internal:host-gateway'
runtime: nvidia
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
- PUID=1000
- PGID=1000
- TZ=America/Los_Angeles
- JELLYFIN_PublishedServerUrl=https://flix.fig.systems
volumes:
- ./config:/config
- ./cache:/cache
# Media folders (read-only)
- /media/movies:/media/movies:ro
- /media/tv:/media/tv:ro
- /media/music:/media/music:ro
- /media/photos:/media/photos:ro
- /media/homemovies:/media/homemovies:ro
ports:
- "8096:8096"
- "7359:7359/udp" # Optional - for autodiscovery
restart: unless-stopped
networks:
- homelab
labels:
traefik.enable: true
traefik.http.routers.jellyfin.rule: Host(`flix.fig.systems`) || Host(`flix.edfig.dev`)
traefik.http.routers.jellyfin.entrypoints: websecure
traefik.http.routers.jellyfin.tls.certresolver: letsencrypt
traefik.http.services.jellyfin.loadbalancer.server.port: 8096
# Note: Jellyfin has its own auth system, SSO middleware disabled by default
# Uncomment the line below to enable SSO (requires users to auth via tinyauth first)
# traefik.http.routers.jellyfin.middlewares: tinyauth
# Uncomment for NVIDIA GPU transcoding
# runtime: nvidia
# deploy:
# resources:
# reservations:
# devices:
# - driver: nvidia
# count: all
# capabilities: [gpu]
networks:
homelab:
external: true

View file

@ -0,0 +1,28 @@
# Jellyseerr - Media Request Management for Jellyfin
# Docs: https://hub.docker.com/r/fallenbagel/jellyseerr
services:
jellyseerr:
container_name: jellyseerr
image: fallenbagel/jellyseerr:latest
environment:
- LOG_LEVEL=info
- TZ=America/Los_Angeles
- PUID=1000
- PGID=1000
volumes:
- ./config:/app/config
restart: unless-stopped
networks:
- homelab
labels:
traefik.enable: true
traefik.http.routers.jellyseerr.rule: Host(`requests.fig.systems`) || Host(`requests.edfig.dev`)
traefik.http.routers.jellyseerr.entrypoints: websecure
traefik.http.routers.jellyseerr.tls.certresolver: letsencrypt
traefik.http.services.jellyseerr.loadbalancer.server.port: 5055
traefik.http.routers.jellyseerr.middlewares: tinyauth
networks:
homelab:
external: true

View file

@ -1,14 +0,0 @@
# https://hub.docker.com/r/fallenbagel/jellyseerr
services:
jellyseerr:
image: fallenbagel/jellyseerr:latest
container_name: jellyseerr
environment:
- LOG_LEVEL=debug
- TZ=America/Los_Angeles
ports:
- 8002:5055
volumes:
- /path/to/appdata/config:/app/config
restart: unless-stopped

View file

@ -0,0 +1,27 @@
# FreshRSS - Self-hosted RSS feed aggregator
# Docs: https://docs.linuxserver.io/images/docker-freshrss/
services:
freshrss:
container_name: freshrss
image: lscr.io/linuxserver/freshrss:latest
environment:
- PUID=1000
- PGID=1000
- TZ=America/Los_Angeles
volumes:
- ./config:/config
restart: unless-stopped
networks:
- homelab
labels:
traefik.enable: true
traefik.http.routers.freshrss.rule: Host(`rss.fig.systems`) || Host(`rss.edfig.dev`)
traefik.http.routers.freshrss.entrypoints: websecure
traefik.http.routers.freshrss.tls.certresolver: letsencrypt
traefik.http.services.freshrss.loadbalancer.server.port: 80
traefik.http.routers.freshrss.middlewares: tinyauth
networks:
homelab:
external: true

View file

@ -1,14 +0,0 @@
# https://docs.linuxserver.io/images/docker-freshrss/
---
services:
freshrss:
image: lscr.io/linuxserver/freshrss:latest
container_name: freshrss
environment:
- PUID=1000
- PGID=1000
volumes:
- /path/to/freshrss/config:/config
ports:
- 8000:80
restart: unless-stopped

View file

@ -0,0 +1,28 @@
# Booklore - Book tracking and management
# Docs: https://github.com/lorebooks/booklore
services:
booklore:
container_name: booklore
image: ghcr.io/lorebooks/booklore:latest
environment:
- PUID=1000
- PGID=1000
- TZ=America/Los_Angeles
volumes:
- ./config:/config
- /media/books:/books:ro
restart: unless-stopped
networks:
- homelab
labels:
traefik.enable: true
traefik.http.routers.booklore.rule: Host(`booklore.fig.systems`) || Host(`booklore.edfig.dev`)
traefik.http.routers.booklore.entrypoints: websecure
traefik.http.routers.booklore.tls.certresolver: letsencrypt
traefik.http.services.booklore.loadbalancer.server.port: 3000
traefik.http.routers.booklore.middlewares: tinyauth
networks:
homelab:
external: true

View file

@ -0,0 +1,30 @@
# Calibre-web - Web app for browsing, reading and downloading eBooks
# Docs: https://hub.docker.com/r/linuxserver/calibre-web
services:
calibre-web:
container_name: calibre-web
image: lscr.io/linuxserver/calibre-web:latest
environment:
- PUID=1000
- PGID=1000
- TZ=America/Los_Angeles
- DOCKER_MODS=linuxserver/mods:universal-calibre
- OAUTHLIB_RELAX_TOKEN_SCOPE=1
volumes:
- ./config:/config
- /media/books:/books
restart: unless-stopped
networks:
- homelab
labels:
traefik.enable: true
traefik.http.routers.calibre-web.rule: Host(`books.fig.systems`) || Host(`books.edfig.dev`)
traefik.http.routers.calibre-web.entrypoints: websecure
traefik.http.routers.calibre-web.tls.certresolver: letsencrypt
traefik.http.services.calibre-web.loadbalancer.server.port: 8083
traefik.http.routers.calibre-web.middlewares: tinyauth
networks:
homelab:
external: true

View file

@ -1,19 +0,0 @@
# https://hub.docker.com/r/linuxserver/calibre-web
---
services:
calibre-web:
image: lscr.io/linuxserver/calibre-web:latest
container_name: calibre-web
environment:
- PUID=1000
- PGID=1000
- TZ=Etc/UTC
- DOCKER_MODS=linuxserver/mods:universal-calibre #optional
- OAUTHLIB_RELAX_TOKEN_SCOPE=1 #optional
volumes:
- /path/to/calibre-web/data:/config
- /path/to/calibre/library:/books
ports:
- 8002:8083
restart: unless-stopped

View file

@ -0,0 +1,29 @@
# File Browser - Web-based file manager
# Docs: https://filebrowser.org/
services:
filebrowser:
container_name: filebrowser
image: filebrowser/filebrowser:latest
environment:
- PUID=1000
- PGID=1000
- TZ=America/Los_Angeles
volumes:
- ./config:/config
- ./database:/database
- /media:/srv
restart: unless-stopped
networks:
- homelab
labels:
traefik.enable: true
traefik.http.routers.filebrowser.rule: Host(`files.fig.systems`) || Host(`files.edfig.dev`)
traefik.http.routers.filebrowser.entrypoints: websecure
traefik.http.routers.filebrowser.tls.certresolver: letsencrypt
traefik.http.services.filebrowser.loadbalancer.server.port: 80
traefik.http.routers.filebrowser.middlewares: tinyauth
networks:
homelab:
external: true

View file

@ -1,11 +0,0 @@
services:
filebrowser:
image:
container_name: filebrowser
restart: unless-stopped
# ports:
# - ":"
# volumes:
# - ./data:/data
# environment:
# - VARIABLE=value

View file

@ -1,29 +1,64 @@
# https://docs.linkwarden.app/self-hosting/installation
# Linkwarden - Collaborative bookmark manager
# Docs: https://docs.linkwarden.app/self-hosting/installation
services:
postgres:
image: postgres:16-alpine
env_file: .env
restart: always
volumes:
- ./pgdata:/var/lib/postgresql/data
linkwarden:
container_name: linkwarden
image: ghcr.io/linkwarden/linkwarden:latest
env_file: .env
environment:
- DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/postgres
restart: always
# build: . # uncomment to build from source
image: ghcr.io/linkwarden/linkwarden:latest # comment to build from source
ports:
- 3000:3000
- DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@linkwarden-postgres:5432/postgres
- TZ=America/Los_Angeles
volumes:
- ./data:/data/data
depends_on:
- postgres
- meilisearch
meilisearch:
image: getmeili/meilisearch:v1.12.8
- linkwarden-postgres
- linkwarden-meilisearch
restart: always
env_file:
- .env
networks:
- homelab
- linkwarden_internal
labels:
traefik.enable: true
traefik.docker.network: homelab
traefik.http.routers.linkwarden.rule: Host(`links.fig.systems`) || Host(`links.edfig.dev`)
traefik.http.routers.linkwarden.entrypoints: websecure
traefik.http.routers.linkwarden.tls.certresolver: letsencrypt
traefik.http.services.linkwarden.loadbalancer.server.port: 3000
traefik.http.routers.linkwarden.middlewares: tinyauth
linkwarden-postgres:
container_name: linkwarden-postgres
image: postgres:16-alpine
env_file: .env
environment:
- TZ=America/Los_Angeles
volumes:
- ./pgdata:/var/lib/postgresql/data
restart: always
networks:
- linkwarden_internal
healthcheck:
test: ["CMD-SHELL", "pg_isready -h localhost -U postgres"]
interval: 10s
timeout: 5s
retries: 5
linkwarden-meilisearch:
container_name: linkwarden-meilisearch
image: getmeili/meilisearch:v1.12.8
env_file: .env
environment:
- TZ=America/Los_Angeles
volumes:
- ./meili_data:/meili_data
restart: always
networks:
- linkwarden_internal
networks:
homelab:
external: true
linkwarden_internal:
name: linkwarden_internal
driver: bridge

View file

@ -0,0 +1,30 @@
# LubeLogger - Vehicle maintenance and fuel tracking
# Docs: https://github.com/hargata/lubelogger
services:
lubelogger:
container_name: lubelogger
image: ghcr.io/hargata/lubelogger:latest
environment:
- TZ=America/Los_Angeles
volumes:
- data:/App/data
- keys:/root/.aspnet/DataProtection-Keys
restart: unless-stopped
networks:
- homelab
labels:
traefik.enable: true
traefik.http.routers.lubelogger.rule: Host(`garage.fig.systems`) || Host(`garage.edfig.dev`)
traefik.http.routers.lubelogger.entrypoints: websecure
traefik.http.routers.lubelogger.tls.certresolver: letsencrypt
traefik.http.services.lubelogger.loadbalancer.server.port: 8080
traefik.http.routers.lubelogger.middlewares: tinyauth
networks:
homelab:
external: true
volumes:
data:
keys:

View file

@ -1,15 +0,0 @@
---
services:
app:
image: ghcr.io/hargata/lubelogger:latest
restart: unless-stopped
volumes:
- data:/App/data
- keys:/root/.aspnet/DataProtection-Keys
ports:
- 8007:8080
volumes:
data:
keys:

View file

@ -1,11 +1,11 @@
# MicroBin - Encrypted pastebin with file upload support
# Docs: https://github.com/szabodanika/microbin
services:
microbin:
container_name: microbin
image: danielszabo99/microbin:latest
restart: always
ports:
- "${MICROBIN_PORT}:8080"
volumes:
- ./microbin-data:/app/microbin_data
env_file: .env
environment:
MICROBIN_BASIC_AUTH_USERNAME: ${MICROBIN_BASIC_AUTH_USERNAME}
MICROBIN_BASIC_AUTH_PASSWORD: ${MICROBIN_BASIC_AUTH_PASSWORD}
@ -45,4 +45,20 @@ services:
MICROBIN_ENCRYPTION_SERVER_SIDE: ${MICROBIN_ENCRYPTION_SERVER_SIDE}
MICROBIN_MAX_FILE_SIZE_ENCRYPTED_MB: ${MICROBIN_MAX_FILE_SIZE_ENCRYPTED_MB}
MICROBIN_MAX_FILE_SIZE_UNENCRYPTED_MB: ${MICROBIN_MAX_FILE_SIZE_UNENCRYPTED_MB}
volumes:
- ./microbin-data:/app/microbin_data
restart: always
networks:
- homelab
labels:
traefik.enable: true
traefik.http.routers.microbin.rule: Host(`paste.fig.systems`) || Host(`paste.edfig.dev`)
traefik.http.routers.microbin.entrypoints: websecure
traefik.http.routers.microbin.tls.certresolver: letsencrypt
traefik.http.services.microbin.loadbalancer.server.port: 8080
# Note: MicroBin has its own auth, SSO disabled by default
# traefik.http.routers.microbin.middlewares: tinyauth
networks:
homelab:
external: true

View file

@ -1,61 +1,76 @@
# RSSHub - RSS feed generator for everything
# Docs: https://docs.rsshub.app/
services:
rsshub:
# two ways to enable puppeteer:
# * comment out marked lines, then use this image instead: diygod/rsshub:chromium-bundled
# * (consumes more disk space and memory) leave everything unchanged
container_name: rsshub
# Using chromium-bundled image for full puppeteer support
image: diygod/rsshub:chromium-bundled
restart: always
ports:
- 1200:1200
environment:
NODE_ENV: production
CACHE_TYPE: redis
REDIS_URL: redis://redis:6379/
PUPPETEER_WS_ENDPOINT: ws://browserless:3000 # marked
healthcheck:
test:
- CMD
- curl
- -f
- http://localhost:1200/healthz
interval: 30s
timeout: 10s
retries: 3
REDIS_URL: redis://rsshub-redis:6379/
PUPPETEER_WS_ENDPOINT: ws://rsshub-browserless:3000
TZ: America/Los_Angeles
restart: always
networks:
- homelab
- rsshub_internal
depends_on:
- redis
- browserless # marked
browserless:
# marked
image: browserless/chrome # marked
restart: always # marked
ulimits:
# marked
core:
# marked
hard: 0 # marked
soft: 0 # marked
- rsshub-redis
- rsshub-browserless
healthcheck:
test:
- CMD
- curl
- -f
- http://localhost:3000/pressure
test: ["CMD", "curl", "-f", "http://localhost:1200/healthz"]
interval: 30s
timeout: 10s
retries: 3
redis:
labels:
traefik.enable: true
traefik.docker.network: homelab
traefik.http.routers.rsshub.rule: Host(`rsshub.fig.systems`) || Host(`rsshub.edfig.dev`)
traefik.http.routers.rsshub.entrypoints: websecure
traefik.http.routers.rsshub.tls.certresolver: letsencrypt
traefik.http.services.rsshub.loadbalancer.server.port: 1200
# Optional: enable SSO (may interfere with RSS readers)
# traefik.http.routers.rsshub.middlewares: tinyauth
rsshub-browserless:
container_name: rsshub-browserless
image: browserless/chrome
restart: always
networks:
- rsshub_internal
ulimits:
core:
hard: 0
soft: 0
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/pressure"]
interval: 30s
timeout: 10s
retries: 3
rsshub-redis:
container_name: rsshub-redis
image: redis:alpine
restart: always
networks:
- rsshub_internal
volumes:
- redis-data:/data
healthcheck:
test:
- CMD
- redis-cli
- ping
test: ["CMD", "redis-cli", "ping"]
interval: 30s
timeout: 10s
retries: 5
start_period: 5s
networks:
homelab:
external: true
rsshub_internal:
name: rsshub_internal
driver: bridge
volumes:
redis-data: null
redis-data:

View file

@ -0,0 +1,58 @@
# Vikunja - The open-source, self-hostable to-do app
# Docs: https://vikunja.io/docs/full-docker-example/
services:
vikunja:
container_name: vikunja
image: vikunja/vikunja:latest
environment:
VIKUNJA_SERVICE_PUBLICURL: https://tasks.fig.systems
VIKUNJA_DATABASE_HOST: vikunja-db
VIKUNJA_DATABASE_PASSWORD: changeme_please_set_secure_password
VIKUNJA_DATABASE_TYPE: postgres
VIKUNJA_DATABASE_USER: vikunja
VIKUNJA_DATABASE_DATABASE: vikunja
VIKUNJA_SERVICE_JWTSECRET: changeme_please_set_random_jwt_secret
TZ: America/Los_Angeles
volumes:
- ./files:/app/vikunja/files
depends_on:
vikunja-db:
condition: service_healthy
restart: unless-stopped
networks:
- homelab
- vikunja_internal
labels:
traefik.enable: true
traefik.docker.network: homelab
traefik.http.routers.vikunja.rule: Host(`tasks.fig.systems`) || Host(`tasks.edfig.dev`)
traefik.http.routers.vikunja.entrypoints: websecure
traefik.http.routers.vikunja.tls.certresolver: letsencrypt
traefik.http.services.vikunja.loadbalancer.server.port: 3456
traefik.http.routers.vikunja.middlewares: tinyauth
vikunja-db:
container_name: vikunja-db
image: postgres:18
environment:
POSTGRES_PASSWORD: changeme_please_set_secure_password
POSTGRES_USER: vikunja
POSTGRES_DB: vikunja
TZ: America/Los_Angeles
volumes:
- ./db:/var/lib/postgresql/data
restart: unless-stopped
networks:
- vikunja_internal
healthcheck:
test: ["CMD-SHELL", "pg_isready -h localhost -U $$POSTGRES_USER"]
interval: 2s
start_period: 30s
networks:
homelab:
external: true
vikunja_internal:
name: vikunja_internal
driver: bridge

View file

@ -1,32 +0,0 @@
# https://vikunja.io/docs/full-docker-example/#example-without-any-proxy
services:
vikunja:
image: vikunja/vikunja
environment:
VIKUNJA_SERVICE_PUBLICURL: http://<the public ip or host where Vikunja is reachable>
VIKUNJA_DATABASE_HOST: db
VIKUNJA_DATABASE_PASSWORD: changeme
VIKUNJA_DATABASE_TYPE: postgres
VIKUNJA_DATABASE_USER: vikunja
VIKUNJA_DATABASE_DATABASE: vikunja
VIKUNJA_SERVICE_JWTSECRET: <a super secure random secret>
ports:
- 3456:3456
volumes:
- ./files:/app/vikunja/files
depends_on:
db:
condition: service_healthy
restart: unless-stopped
db:
image: postgres:18
environment:
POSTGRES_PASSWORD: changeme
POSTGRES_USER: vikunja
volumes:
- ./db:/var/lib/postgresql/data
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -h localhost -U $$POSTGRES_USER"]
interval: 2s
start_period: 30s