Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions config/traefik/dynamic/middlewares.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,6 @@
http:
middlewares:

# -------------------------------------------------------------------------
# BasicAuth — Traefik Dashboard protection
# Generate hash: echo $(htpasswd -nb admin PASSWORD) | sed -e s/\$/\$\$/g
# Then set TRAEFIK_DASHBOARD_PASSWORD_HASH in .env
# -------------------------------------------------------------------------
traefik-auth:
basicAuth:
usersFile: /dynamic/.htpasswd
removeHeader: true # Strip Authorization header from upstream request

# -------------------------------------------------------------------------
# Authentik ForwardAuth
# Protects any service — redirects unauthenticated requests to SSO login.
Expand Down
2 changes: 1 addition & 1 deletion config/traefik/traefik.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ certificatesResolvers:
# -----------------------------------------------------------------------------
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
endpoint: "tcp://socket-proxy:2375"
exposedByDefault: false # Containers must opt-in with traefik.enable=true
network: proxy # Default network for routing
watch: true
Expand Down
5 changes: 5 additions & 0 deletions stacks/base/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
DOMAIN=example.com
ACME_EMAIL=admin@example.com
TRAEFIK_AUTH=admin:$$apr1$$H6O1Q0x2$$3.jW8.p.1.1.1.1.1.1.1.
TZ=Asia/Shanghai
WATCHTOWER_NOTIFICATION_URL=
102 changes: 38 additions & 64 deletions stacks/base/README.md
Original file line number Diff line number Diff line change
@@ -1,76 +1,50 @@
# Base Infrastructure Stack
# Base Stack

The foundation of HomeLab Stack. Must be deployed **before any other stack**.
This is the foundational stack for the entire HomeLab project. All other services depend on this stack.

## What's Included
## Services Included

| Service | Version | URL | Purpose |
|---------|---------|-----|---------|
| Traefik | 3.1 | `traefik.<DOMAIN>` | Reverse proxy + TLS termination |
| Portainer CE | 2.21 | `portainer.<DOMAIN>` | Docker management UI |
| Watchtower | latest-stable | — | Automatic container updates |
- **Traefik (v3.1.6)**: Reverse Proxy and automatic HTTPS certificate generation.
- **Portainer CE (2.21.3)**: Docker container management UI.
- **Watchtower (1.7.1)**: Automatic updates for Docker containers.
- **Socket Proxy (0.2.0)**: Securely isolates the Docker socket, limiting access to only what Traefik needs.

## Architecture
## Setup Instructions

```
Internet
[Traefik :443]
│ TLS termination (Let's Encrypt)
│ ForwardAuth → Authentik (optional)
├──► portainer.<DOMAIN> → Portainer
├──► traefik.<DOMAIN> → Traefik Dashboard
└──► *..<DOMAIN> → Other stacks via 'proxy' network
1. Copy the sample environment file:
```bash
cp .env.example .env
```
2. Update the `.env` file with your domain, email, and Traefik dashboard basic auth credentials.
3. Start the stack:
```bash
docker network create proxy # if not already created
docker compose up -d
```

[proxy] ← shared Docker network — all stacks attach here
```
## DNS Configuration

## Prerequisites
To make your services accessible, configure your DNS provider as follows:

- Docker >= 24.0 with Compose v2 plugin
- Ports 80 and 443 open on your firewall
- A domain pointing to your server's IP (A record)
- `./scripts/setup-env.sh` completed (creates `.env` and `acme.json`)
1. Create an `A` record for your root domain pointing to your server's IP address.
2. Create a CNAME or Wildcard `A` record (`*.example.com`) pointing to your server's IP to handle all subdomains dynamically.
3. Ensure ports 80 and 443 are port-forwarded from your router to the server if hosting locally.

## Quick Start
## Certificate Configuration (Let's Encrypt)

```bash
# From repo root — recommended (runs check-deps + setup-env first)
./install.sh
Traefik is pre-configured to handle certificates automatically via Let's Encrypt.

# Or manually:
cd stacks/base
ln -sf ../../.env .env # share root .env
docker compose up -d
```
### HTTP Challenge (Default)
By default, Traefik uses the HTTP-01 challenge. It requires port 80 to be accessible from the internet. No extra configuration is needed beyond setting `ACME_EMAIL` in the `.env` file.

## Configuration

### Environment Variables (`.env`)

| Variable | Required | Description |
|----------|----------|-------------|
| `DOMAIN` | ✅ | Base domain, e.g. `home.example.com` |
| `ACME_EMAIL` | ✅ | Email for Let's Encrypt notifications |
| `TRAEFIK_DASHBOARD_USER` | ✅ | Dashboard login username |
| `TRAEFIK_DASHBOARD_PASSWORD_HASH` | ✅ | Bcrypt hash — see below |
| `TZ` | ✅ | Timezone, e.g. `Asia/Shanghai` |
| `CN_MODE` | — | `true` to use CN Docker mirrors |

### Generate Dashboard Password Hash

```bash
# Install htpasswd (Debian/Ubuntu)
sudo apt-get install -y apache2-utils

# Generate hash (replace 'yourpassword')
htpasswd -nbB admin 'yourpassword' | sed -e 's/\$$/\$\$\$/g'

# Paste output into .env as TRAEFIK_DASHBOARD_PASSWORD_HASH
```

### TLS Certificates

Traefik uses Let's Encrypt HTTP-01 challenge by default. Certificates are stored in
### DNS Challenge (Wildcard Certificates)
If you want wildcard certificates or cannot expose port 80:
1. Open `config/traefik/traefik.yml` and enable the `letsencrypt-dns` section.
2. Provide your DNS provider name.
3. Add the required DNS API credentials as environment variables to the Traefik service in `docker-compose.yml`. For example, for Cloudflare:
```yaml
environment:
- CF_API_EMAIL=your-email@example.com
- CF_DNS_API_TOKEN=your-cloudflare-api-token
```
4. Change the `certresolver` label on your containers to use `letsencrypt-dns` instead of `letsencrypt`.
59 changes: 28 additions & 31 deletions stacks/base/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,27 +1,40 @@
# =============================================================================
# HomeLab Stack — Base Infrastructure
# Services: Traefik (reverse proxy) + Portainer (container management)
# + Watchtower (auto-updates)
#
# Prerequisites:
# 1. cp .env.example .env && fill in required values
# 2. ./scripts/check-deps.sh
# 3. docker network create proxy
# 4. touch config/traefik/acme.json && chmod 600 config/traefik/acme.json
# 5. docker compose up -d
# + Watchtower (auto-updates) + Socket Proxy (security)
# =============================================================================

services:

# ---------------------------------------------------------------------------
# Docker Socket Proxy
# ---------------------------------------------------------------------------
socket-proxy:
image: tecnativa/docker-socket-proxy:0.2.0
container_name: socket-proxy
restart: unless-stopped
networks:
- proxy
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
- CONTAINERS=1
- NETWORKS=1
- SERVICES=1
- TASKS=1
- INFO=1
- PLUGINS=1
- VOLUMES=1

# ---------------------------------------------------------------------------
# Traefik — Reverse Proxy & TLS Termination
# Dashboard: https://traefik.${DOMAIN}
# Docs: https://doc.traefik.io/traefik/
# ---------------------------------------------------------------------------
traefik:
image: traefik:v3.1.6
container_name: traefik
restart: unless-stopped
depends_on:
- socket-proxy
networks:
- proxy
ports:
Expand All @@ -30,21 +43,18 @@ services:
environment:
- TZ=${TZ}
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ../../config/traefik/traefik.yml:/traefik.yml:ro
- ../../config/traefik/dynamic:/dynamic:ro
- ../../config/traefik/acme.json:/acme.json
- traefik-logs:/var/log/traefik
labels:
# Enable Traefik for this container
- "traefik.enable=true"
# Router: HTTPS dashboard
- "traefik.http.routers.traefik-dashboard.rule=Host(`traefik.${DOMAIN}`)"
- "traefik.http.routers.traefik-dashboard.entrypoints=websecure"
- "traefik.http.routers.traefik-dashboard.tls.certresolver=letsencrypt"
- "traefik.http.routers.traefik-dashboard.service=api@internal"
# Protect dashboard with BasicAuth
- "traefik.http.routers.traefik-dashboard.middlewares=traefik-auth@file,security-headers@file"
- "traefik.http.middlewares.traefik-auth.basicauth.users=${TRAEFIK_AUTH}"
- "traefik.http.routers.traefik-dashboard.middlewares=traefik-auth,security-headers@file"
healthcheck:
test: ["CMD", "traefik", "healthcheck", "--ping"]
interval: 30s
Expand All @@ -54,11 +64,9 @@ services:

# ---------------------------------------------------------------------------
# Portainer — Docker Management UI
# URL: https://portainer.${DOMAIN}
# First login: set admin password within 5 minutes
# ---------------------------------------------------------------------------
portainer:
image: portainer/portainer-ce:2.21.4
image: portainer/portainer-ce:2.21.3
container_name: portainer
restart: unless-stopped
networks:
Expand All @@ -83,8 +91,6 @@ services:

# ---------------------------------------------------------------------------
# Watchtower — Automatic Container Updates
# Checks for image updates daily at 4:00 AM
# Notifications sent via configured notifier (see .env)
# ---------------------------------------------------------------------------
watchtower:
image: containrrr/watchtower:1.7.1
Expand All @@ -96,27 +102,18 @@ services:
- TZ=${TZ}
- WATCHTOWER_CLEANUP=true
- WATCHTOWER_INCLUDE_RESTARTING=true
- WATCHTOWER_SCHEDULE=0 0 4 * * *
- WATCHTOWER_SCHEDULE=0 0 3 * * *
- WATCHTOWER_TIMEOUT=60s
- WATCHTOWER_NOTIFICATIONS_LEVEL=warn
# Scope: only update containers with this label
- WATCHTOWER_LABEL_ENABLE=true
- WATCHTOWER_NOTIFICATION_URL=${WATCHTOWER_NOTIFICATION_URL:-}
- DOCKER_API_VERSION=1.44
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
labels:
- "com.centurylinklabs.watchtower.enable=true"
healthcheck:
test: ["CMD", "/watchtower", "--health-check"]
interval: 30s
timeout: 10s
retries: 3

# =============================================================================
# Networks
# NOTE: 'proxy' network is EXTERNAL — create it before first run:
# docker network create proxy
# All other stacks join this network to be accessible via Traefik.
# =============================================================================
networks:
proxy:
Expand Down