diff --git a/config/traefik/dynamic/middlewares.yml b/config/traefik/dynamic/middlewares.yml index 2d1367ee..059ab4c4 100644 --- a/config/traefik/dynamic/middlewares.yml +++ b/config/traefik/dynamic/middlewares.yml @@ -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. diff --git a/config/traefik/traefik.yml b/config/traefik/traefik.yml index 3e71c28f..43095067 100644 --- a/config/traefik/traefik.yml +++ b/config/traefik/traefik.yml @@ -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 diff --git a/stacks/base/.env.example b/stacks/base/.env.example new file mode 100644 index 00000000..9b25dd48 --- /dev/null +++ b/stacks/base/.env.example @@ -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= diff --git a/stacks/base/README.md b/stacks/base/README.md index 19505b80..ba2d5a2a 100644 --- a/stacks/base/README.md +++ b/stacks/base/README.md @@ -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.` | Reverse proxy + TLS termination | -| Portainer CE | 2.21 | `portainer.` | 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. → Portainer - ├──► traefik. → Traefik Dashboard - └──► *.. → 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 \ No newline at end of file +### 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`. \ No newline at end of file diff --git a/stacks/base/docker-compose.yml b/stacks/base/docker-compose.yml index 7185e836..7854b4e8 100644 --- a/stacks/base/docker-compose.yml +++ b/stacks/base/docker-compose.yml @@ -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: @@ -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 @@ -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: @@ -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 @@ -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: