Skip to content

Ansible-based homelab infrastructure automation system using Docker Swarm for container orchestration and Traefik for reverse proxy with dynamic routing.

Notifications You must be signed in to change notification settings

muhfaris/homelap-setup

Repository files navigation

Homelab Infrastructure Automation

Ansible-based homelab infrastructure automation system using Docker Swarm for container orchestration and Traefik for reverse proxy with dynamic routing.

Table of Contents

Features

  • Docker Swarm Orchestration: Automatic swarm initialization and overlay networking
  • Traefik Reverse Proxy: Dynamic file-based routing with automatic service discovery
  • Template-driven Deployment: Consistent service deployment using Jinja2 templates
  • Hot Reload: Route updates without service restarts
  • Automated Maintenance: Log rotation and system cleanup via cron jobs

How It Works

This system is designed to automate the deployment of services to a Docker Swarm cluster. Here’s a visual overview of the workflow when you add a new service:

graph TD
    subgraph "Your Local Machine"
        A["<br><b>You</b><br>"] -- "1. Run ansible-playbook" --> B(vars/hello.yaml + playbook command)
    end

    subgraph "Homelab Server"
        C[Ansible `service` Role] -->|Uses| D(Jinja2 Templates)
        C -->|2. Generates| E(services/hello.yaml)
        C -->|2. Generates| F(dynamic/routers/hello.yaml)
        E -->|4. Deploy| G(Docker Swarm)
        F -->|3. Hot Reload| H(Traefik)
    end

    I["<br><b>End User</b><br>"]

    B -- SSH --> C
    G --> H
    H -->|5. Access Service| I
Loading

Workflow Steps:

  1. Run Playbook: You execute the ansible-playbook command from your local machine, targeting your homelab server. You pass a service definition file (e.g., vars/hello.yaml) containing all the necessary variables for your new service.
  2. Generate Configs: Ansible connects to the server via SSH. The service role uses Jinja2 templates to generate two critical files based on your variables:
    • A Docker Compose file in /opt/homelab/services/ that defines your application as a Docker Swarm service.
    • A Traefik routing configuration file in /opt/homelab/dynamic/routers/ that tells Traefik how to route traffic to your new service.
  3. Hot Reload: Traefik is configured to use Docker for service discovery and a file provider for dynamic routing. It automatically detects the new routing file and reloads its configuration on the fly, without requiring a restart.
  4. Deploy: The Ansible playbook uses the docker_stack module to deploy your service to the Docker Swarm cluster. Docker Swarm ensures the service is running according to your definition (e.g., correct image, replicas, etc.).
  5. Access Service: The service is now live and accessible to end-users through the Traefik reverse proxy at the host and path you defined.

Quick Start

Prerequisites

  • Ansible installed on control machine
  • SSH access to target server (configure in inventory.ini)
  • Docker installed on target server

Configuration

Copy and configure the inventory file:

cp example.inventory.ini inventory.ini
# Edit inventory.ini with your server details (host, user, SSH key path)

⚠️ Security Note: Keep inventory.ini secure as it contains sensitive connection details.

Initial Setup

Bootstrap the entire homelab infrastructure:

ansible-playbook -i inventory.ini playbooks/initialize.yaml -e "main_domain=yourdomain.com"

This will:

  • Create necessary directories
  • Initialize Docker Swarm
  • Set up overlay network
  • Deploy Traefik with dynamic routing (dashboard accessible at yourdomain.com/traefik/dashboard)
  • Configure log rotation and maintenance cron jobs

Required: Replace yourdomain.com with your actual domain where Traefik will be accessible.

Service Management

Adding Services

Create a vars file for your service in the vars/ directory:

# vars/hello.yml
name: hello
image: devmuhfaris/hello-world
port: 80
host: apps.yourdomain.com
path: /hello
replicas: 1

For services with static assets (Next.js, React, etc.), add assets routing:

# vars/webapp.yml
name: webapp
image: yourorg/webapp:latest
port: 3000
host: apps.yourdomain.com
path: /webapp
replicas: 2
priority: 2000
assets_paths:
  - "/_next/"
  - "/favicon.ico"
  - "/robots.txt"
  - "/static/"

Deploy the service:

ansible-playbook -i inventory.ini playbooks/add-service.yaml --extra-vars @vars/hello.yml

Service Variables

Required:

  • name: Service identifier
  • image: Docker image reference
  • port: Internal service port
  • host: External hostname
  • path: URL path for routing

Optional:

  • replicas: Number of service replicas (default: 1)
  • priority: Traefik routing priority (default: 2000)
  • assets_paths: List of asset paths for separate routing
  • assets_priority: Priority for assets router (default: priority + 1000)
  • cpu: CPU allocation per replica (default: "0.25")
  • memory: Memory allocation per replica (default: "64M")
  • command: The full command to run in the container. You are responsible for including any flags needed to read a configuration file.
  • service_config: A dictionary of structured configuration data to be mounted as a file into the container.
  • service_config_format: The format of the configuration file (yaml or json). Defaults to yaml.
  • service_config_target_path: The full path inside the container where the configuration file will be mounted (e.g., /app/config.yaml). Defaults to /<name>.config.<format>.
  • service_config_name_override: Allows you to specify a custom name for the Docker Swarm config resource, overriding the auto-generated hashed name.
  • internal_only: When true, skips Traefik routing and HTTP labels (for DBs/queues). host, path, and port are not required.
  • env / env_vars: Map of environment variables for the container. Avoid using environment as a var name (reserved by Jinja/Ansible).
  • ports: List of published ports (e.g., [{ target: 5432, published: 5432 }]). Optional fields: protocol, mode. If mode is omitted, Swarm defaults to ingress.
  • volumes: List of named volume mounts (e.g., [{ name: pgdata, target: /var/lib/postgresql/data }]).
  • healthcheck: Compose-style healthcheck fields: test, interval, timeout, retries, start_period.

Updating Services

Modify your vars file and redeploy:

ansible-playbook -i inventory.ini playbooks/add-service.yaml --extra-vars @vars/hello.yml

Changes to routing configuration trigger hot reloads. Changes to image or replicas trigger rolling updates.

Internal Services (Databases, Caches)

For services not exposed via Traefik, set internal_only: true and provide only what the container needs (e.g., env, volumes, optional published ports):

# vars/redis.yml
name: redis
image: redis:7-alpine
internal_only: true
command: "redis-server --appendonly yes"
volumes:
  - name: redis-data
    target: /data
ports:  # optional, if you want LAN access
  - target: 6379
    published: 6379
    mode: host

Postgres example with env variables:

# vars/db_idcards.yaml
name: db_idcards
image: postgres:16-alpine
internal_only: true
env:
  POSTGRES_DB: app
  POSTGRES_USER: app
  POSTGRES_PASSWORD: change-me
volumes:
  - name: db_idcards_data
    target: /var/lib/postgresql/data

Advanced: Using Configuration Files

For applications requiring a configuration file, you have full control over how it's mounted and used. The system no longer automatically injects any command-line flags, giving you the flexibility to support any application.

How It Works

When you define service_config in your service's variable file, the system creates a Docker Swarm config object from your data. You then tell your application how to use it.

  1. Define the Configuration Content: Add your structured data to the service_config variable.
  2. Specify the Mount Path: Use service_config_target_path to define exactly where the configuration file should be placed inside your container. This path should match what your application expects.
  3. Provide the Full Command: Use the command variable to specify the exact command to run your application, including the flag needed to read the config file (e.g., -c, --config-file, etc.).

Example

Here’s an example of a service that uses a custom flag (-c) and a custom path (/etc/my-app/settings.yaml) for its configuration:

# vars/my-app.yml
name: my-app
image: my-org/my-app:latest
port: 8080
host: apps.yourdomain.com
path: /my-app

# 1. Define the config content
service_config:
  database:
    host: db.internal
    port: 5432
  api_keys:
    - key: "key1"
      value: "value1"

# 2. Specify the target path inside the container
service_config_target_path: /etc/my-app/settings.yaml

# 3. Provide the full command, including the custom flag and path
command: "node server.js -c /etc/my-app/settings.yaml"

In this example:

  • A configuration file is created from the service_config data.
  • It is mounted inside the container at /etc/my-app/settings.yaml.
  • The service's command is executed exactly as node server.js -c /etc/my-app/settings.yaml.

This approach gives you the flexibility to work with any application, regardless of its specific command-line arguments.

Directory Structure

/opt/homelab/                   # Root directory 
├── dynamic                     # Traefik dynamic routing
│   └── routers
│       └── hello.yaml 
├── hl-traefik.yaml
└── services                    # Docker Compose files
    └── hello.yaml

/var/log/traefik/               # Traefik logs
├── access.log                  # Access logs  
├── traefik.log                 # Application logs

Architecture

The system uses a modular Ansible role structure:

  • common: Infrastructure bootstrap and maintenance
  • traefik: Reverse proxy configuration and dynamic routing
  • service: Template-driven service deployment

Services are deployed as Docker Swarm services with automatic Traefik integration for routing and load balancing.

About

Ansible-based homelab infrastructure automation system using Docker Swarm for container orchestration and Traefik for reverse proxy with dynamic routing.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages