Stop Wrestling With Homelab Chaos! Use juftin/homelab Instead
Your homelab is a nightmare, isn't it? You've got Plex running on some random port, a reverse proxy you configured six months ago and now fear touching, OAuth scattered across three different tutorials, and every time you want to add a new service, you break three existing ones. You've spent weekends chasing DNS records, crying over certificate errors, and wondering why your media server suddenly can't reach the internet.
What if I told you there's a way to deploy your entire homelab—media, utilities, AI experiments, everything—with a single command?
No more manual port mapping. No more fragmented configurations scattered across a dozen directories. No more praying that docker↗ Bright Coding Blog-compose up doesn't explode your carefully balanced house of cards.
Enter juftin/homelab—a meticulously engineered, profile-driven Docker Compose system that transforms homelab deployment from a chaotic weekend project into a five-minute operation. This isn't just another docker-compose.yml dumped on GitHub. It's a battle-tested architecture that top homelab enthusiasts are quietly adopting to eliminate deployment friction forever.
Ready to discover why everyone is abandoning manual Docker setups? Let's dive deep.
What is juftin/homelab?
juftin/homelab is a comprehensive, open-source homelab deployment framework built around Docker Compose profiles. Created by Justin Flannery, this repository solves the single most painful problem in homelab management: orchestrating dozens of services without losing your sanity.
At its core, juftin/homelab is a single Docker Compose application that uses the include directive to pull in modular service definitions from an apps/ directory. Each service belongs to a Docker Compose profile—a native Docker feature that lets you selectively start groups of related containers. This means you can deploy your core infrastructure, spin up your media stack, or experiment with LLMs, all with surgical precision.
Why is this trending now? Three forces have converged:
- Docker Compose profiles matured in recent versions, making selective service deployment production-ready
- Self-hosting exploded post-2023 as developers reclaimed control from cloud services
- The "homelab as code" movement gained traction, with practitioners demanding GitOps-friendly infrastructure
What sets juftin/homelab apart from the sea of docker-compose.yml gists is its opinionated architecture. Justin didn't just bundle containers together—he engineered a complete ecosystem with Traefik reverse proxy, Google OAuth integration, Cloudflare DNS automation, and logical service grouping that mirrors how real homelabs actually function.
The repository has attracted attention because it solves the "second homelab" problem: your first homelab is fun to build, but your second needs to be maintainable. This project is designed for that second iteration—when you want reliability, security, and the ability to hand your setup to future-you without a 20-page README of tribal knowledge.
Key Features That Make This Insane
Let's dissect what makes juftin/homelab technically superior to ad-hoc deployments:
Profile-Driven Service Architecture
Instead of monolithic compose files or shell scripts that start everything, services are organized into five distinct profiles:
core— Traefik reverse proxy + OAuth authentication. This is your foundation. Without it, nothing else is accessible securely.media— Plex, Sonarr, Radarr, Ombi. Your complete media pipeline from request to stream.utilities— Watchtower (auto-updates), Portainer (container management). The operational layer that keeps everything alive.miscellaneous— ChatGPT Next Web, LibreOffice Online. Productivity tools that don't fit elsewhere.llm— Local LLM experimentation. Disabled by default because this is where your GPU catches fire.
Each profile is independently deployable. Need to restart your media stack without touching your reverse proxy? make media handles it. This granular control is impossible with traditional all-or-nothing compose files.
Security-First Design
Every external service is protected behind Google OAuth via Traefik Forward Auth. No more VPN requirements for basic access. No more exposing services directly to the internet. The secrets/ directory cleanly separates credentials from configuration, following Docker security best practices.
Modular File Organization
The include directive in the root docker-compose.yaml pulls in service definitions from apps/. This means:
- Adding a service? Drop a new YAML file in
apps/, add it to a profile, done. - Removing a service? Delete one file. No grep-ing through 800 lines of compose.
- Debugging? Each service is isolated and readable.
Makefile Automation
Common operations are wrapped in Make targets. You don't need to remember complex docker compose invocations. The Makefile becomes your CLI for the entire homelab.
Persistent Volume Strategy
The appdata/ directory uses per-service subdirectories for persistent storage. This eliminates the volume naming chaos that plagues most Docker deployments. You always know where your data lives, and backups become trivial.
Real-World Use Cases Where This Shines
Scenario 1: The Weekend Media Server Migration
You've been running Plex on a bare-metal install for years. It's creaking, your library database is 40GB, and you're terrified of touching it. With juftin/homelab, you:
- Deploy the
coreprofile on your new server - Copy your Plex config to
appdata/plex/ - Enable the
mediaprofile - Your entire stack is live with HTTPS and OAuth in minutes
The migration that used to take a full weekend now takes an afternoon.
Scenario 2: The "Let Me Try This AI Thing" Experiment
You want to run a local ChatGPT interface but don't want it running 24/7 or interfering with your production services. Simply:
make llm
The llm profile spins up, you experiment, and when you're done, it shuts down without touching anything else. Complete isolation, zero side effects.
Scenario 3: The Multi-User Household
Your partner wants to request movies through Ombi. Your kids need Plex. You need everything locked down. The built-in OAuth with Google whitelisting means:
- Family members authenticate with existing Google accounts
- No password management overhead
- Granular access control via the
secrets/google_oauth.secretfile
Scenario 4: The "I Need to Update Everything" Maintenance Window
Watchtower in the utilities profile can auto-update containers, but for manual control:
make update
This pulls latest images across your enabled profiles, with Portainer giving you visual confirmation of what changed.
Step-by-Step Installation & Setup Guide
Let's get you deployed. This assumes a Linux server with Docker and Docker Compose v2.20+ installed.
Step 1: Clone and Enter the Repository
git clone https://github.com/juftin/homelab.git
cd homelab
Step 2: Create Your Configuration Files
The project uses environment files and secrets for all configuration. Copy the examples:
# Main configuration: domains, ports, feature flags
cp docs/example.env .env
# Sensitive credentials: API keys, OAuth secrets
cp -r docs/example-secrets/ secrets/
Step 3: Configure Your Environment
Edit .env with your specifics. At minimum, you'll need:
# Your domain (must resolve to this server)
DOMAIN=yourdomain.com
# Email for Let's Encrypt certificates
ACME_EMAIL=you@example.com
# Which profiles to enable by default
COMPOSE_PROFILES=core,media,utilities
Step 4: Configure Secrets
The secrets/ directory contains sensitive files that are never committed to Git:
# Cloudflare API key for DNS challenge and DDNS
secrets/cloudflare_api_key.secret
# Google OAuth credentials and allowed email addresses
secrets/google_oauth.secret
For Google OAuth, create credentials at Google Cloud Console and add authorized redirect URIs for:
https://oauth.yourdomain.com/_oauthhttps://oauth.yourdomain.com/_oauth/callback
Step 5: Deploy Your First Profile
Start with core to establish your reverse proxy and authentication:
make core
Verify Traefik is healthy:
docker compose ps
You should see traefik and oauth containers running.
Step 6: Add Services Incrementally
Once core is stable, add media:
make media
Or utilities:
make utilities
Each profile is validated independently before the next deployment.
Step 7: Verify HTTPS and OAuth
Navigate to https://traefik.yourdomain.com. You should hit the Google OAuth login screen. After authentication, Traefik's dashboard appears.
REAL Code Examples From the Repository
Let's examine the actual architecture that makes this system work. These examples are extracted directly from juftin/homelab's implementation.
Example 1: The Root Compose File Using include
The root docker-compose.yaml is remarkably clean because it delegates to modular service files:
# docker-compose.yaml
# This is the entry point for the entire homelab. Instead of defining
# all services here (which would be unmaintainable), it uses the
# 'include' directive to pull in focused service definitions.
include:
- apps/traefik/docker-compose.yaml # Reverse proxy foundation
- apps/oauth.yaml # Authentication layer
- apps/plex.yaml # Media server
- apps/sonarr.yaml # TV show management
- apps/radarr.yaml # Movie management
- apps/ombi.yaml # Media requests
- apps/watchtower.yaml # Auto-updates
- apps/portainer.yaml # Container management
- apps/chat-gpt-next-web.yaml # Local LLM interface
# Global networks and volumes can still be defined here
networks:
default:
name: homelab
driver: bridge
Why this matters: The include directive (available Docker Compose v2.20+) lets you compose your compose. Each included file can be tested independently. If Plex breaks, you debug apps/plex.yaml without scrolling through 500 unrelated lines.
Example 2: Profile Definition in a Service File
Here's how the plex.yaml service file specifies its profile membership:
# apps/plex.yaml
# Plex media server - only starts when the 'media' profile is active
services:
plex:
image: plexinc/pms-docker:latest
container_name: plex
restart: unless-stopped
# CRITICAL: This is the profile assignment
# 'docker compose --profile media up' will include this service
# 'docker compose up' without --profile will skip it
profiles:
- media
environment:
- PLEX_CLAIM=${PLEX_CLAIM}
- PUID=${PUID}
- PGID=${PGID}
volumes:
# Persistent configuration in the organized appdata structure
- ./appdata/plex:/config
# Your actual media libraries
- ${MEDIA_ROOT}/movies:/movies
- ${MEDIA_ROOT}/tv:/tv
networks:
- default
# Traefik labels for automatic reverse proxy configuration
labels:
- "traefik.enable=true"
- "traefik.http.routers.plex.rule=Host(`plex.${DOMAIN}`)"
- "traefik.http.routers.plex.entrypoints=websecure"
- "traefik.http.routers.plex.tls.certresolver=letsencrypt"
The profile magic: Notice profiles: [media]. This single line is what enables selective deployment. Without it, Plex would start with everything else, consuming resources even when you just want core infrastructure.
Example 3: Makefile Targets for Profile Management
The Makefile abstracts Docker Compose commands into intuitive operations:
# Makefile
# Common operations wrapped for convenience and consistency
# Default target: show available commands
.PHONY: help
help:
@echo "Available profiles: core, media, utilities, miscellaneous, llm"
@echo "Usage: make <profile> or make <command>"
# Deploy the core infrastructure (always required first)
.PHONY: core
core:
docker compose --profile core up -d
# Deploy media stack: Plex + *arr suite + Ombi
.PHONY: media
media: core
docker compose --profile media up -d
# Deploy utility services for monitoring and maintenance
.PHONY: utilities
utilities: core
docker compose --profile utilities up -d
# Deploy everything enabled in COMPOSE_PROFILES
.PHONY: all
all:
docker compose --profile core --profile media --profile utilities up -d
# Graceful shutdown of all services
.PHONY: down
down:
docker compose down
# View logs across all services
.PHONY: logs
logs:
docker compose logs -f
# Update all containers to latest images
.PHONY: update
update:
docker compose pull
docker compose up -d
The dependency chain: Notice media: core—this ensures you can't accidentally deploy media without your reverse proxy. The Makefile encodes operational knowledge that would otherwise live in someone's head.
Example 4: Traefik Middleware for OAuth Protection
The OAuth integration happens through Traefik middleware, defined in the rules directory:
# apps/traefik/rules/middlewares.yml
# These middlewares are applied to routes to enforce authentication
http:
middlewares:
# The OAuth forward auth middleware
# Every request is checked against the OAuth service before reaching the backend
oauth-auth:
forwardAuth:
address: "http://oauth:4181"
authResponseHeaders:
- "X-Forwarded-User"
trustForwardHeader: true
# Security headers applied to all responses
security-headers:
headers:
customFrameOptionsValue: "SAMEORIGIN"
contentTypeNosniff: true
browserXssFilter: true
forceSTSHeader: true
stsIncludeSubdomains: true
stsSeconds: 31536000
The security model: This is zero-trust networking for your homelab. No service is reachable without passing OAuth validation. The middleware is declarative—you add labels to services, not configure each application individually.
Advanced Usage & Best Practices
Profile Composition for Development vs. Production
Create environment-specific .env files:
# .env.development
COMPOSE_PROFILES=core,llm
DOMAIN=dev.local
ACME_EMAIL=dev@example.com
# .env.production
COMPOSE_PROFILES=core,media,utilities
DOMAIN=yourdomain.com
ACME_EMAIL=admin@yourdomain.com
Switch with: docker compose --env-file .env.development --profile llm up -d
Backup Strategy
The appdata/ structure makes backups trivial:
# Daily incremental backup of all persistent data
rsync -avz --delete ./appdata/ /backup/homelab/$(date +%Y%m%d)/
Each service's data is isolated, so you can restore Plex without affecting Sonarr.
Resource Constraints for LLM Experiments
The llm profile can consume massive GPU resources. Add to your service definitions:
deploy:
resources:
limits:
memory: 16G
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
Health Checks and Auto-Recovery
Extend services with Docker health checks:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:32400/identity"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
Comparison With Alternatives
| Feature | juftin/homelab | Manual Docker Compose | Kubernetes (k3s) | Ansible + Docker |
|---|---|---|---|---|
| Deployment Complexity | Low (single command) | Medium (custom scripts) | High (YAML engineering) | Medium (playbook maintenance) |
| Profile Selectivity | ✅ Native Docker feature | ❌ Manual scripting | ✅ Namespaces/labels | ❌ Conditional tasks |
| Reverse Proxy Integration | ✅ Built-in Traefik | ❌ Manual setup | ⚠️ Ingress complexity | ❌ Separate configuration |
| OAuth Security | ✅ Google OAuth out-of-box | ❌ Self-configured | ⚠️ OAuth2 Proxy setup | ❌ Additional role needed |
| Service Modularity | ✅ include directive |
❌ Monolithic files | ✅ Helm charts | ⚠️ Role-per-service |
| Learning Curve | Low | Medium | Very High | Medium |
| Resource Overhead | Minimal | Minimal | Moderate (control plane) | Minimal |
| GitOps Friendly | ✅ Yes | ⚠️ Possible | ✅ Native | ✅ Yes |
The verdict: Kubernetes is overkill for most homelabs. Manual compose files become unmaintainable. Ansible adds abstraction without solving the core orchestration problem. juftin/homelab hits the sweet spot of power and simplicity—sophisticated enough for production use, simple enough to understand in an afternoon.
FAQ
What Docker Compose version do I need?
Docker Compose v2.20.0 or later is required for the include directive. Check with docker compose version.
Can I run this without a public domain?
Yes, but you'll lose HTTPS and OAuth functionality. Use *.local domains with self-signed certificates for internal-only deployments.
How do I add a custom service not in the repository?
Create apps/yourservice.yaml, define its profile, add it to the root include list. Follow the existing patterns for Traefik labels.
Is this suitable for ARM devices like Raspberry Pi?
Most services support ARM64, but verify individual images. Plex transcoding requires Intel QuickSync or NVIDIA for hardware acceleration.
Can I use different OAuth providers?
The current implementation uses Traefik Forward Auth with Google. Other providers (GitHub, Authelia) would require modifying the OAuth service configuration.
How do updates work without breaking my configuration?
Persistent data lives in appdata/ which is .gitignored. Pulling repository updates only affects container definitions, never your data.
What's the minimum server spec?
For core + media: 4GB RAM, 2 cores, 20GB storage. Add 8GB+ for llm profile with local models.
Conclusion
The homelab community has been crying out for opinionated, maintainable infrastructure as code. juftin/homelab answers that call with a profile-driven architecture that respects your time and sanity.
What makes this project exceptional isn't any single feature—it's the cohesion. Traefik, OAuth, and Docker Compose profiles aren't novel individually. But combined with modular file organization, Makefile automation, and sensible defaults, they create a system that's genuinely delightful to operate.
If you're still maintaining a monolithic docker-compose.yml that grows more terrifying with each service addition, stop. If you're considering Kubernetes for a single-node homelab, reconsider. The future of homelab deployment is selective, modular, and profile-driven—and juftin/homelab is leading that future today.
Your move. Clone the repository, deploy your first profile, and experience what homelab management should have been all along.
👉 Get juftin/homelab on GitHub
The weekend you save might just be your own.