Introduction
Traefik is a cloud-native reverse proxy and load balancer that integrates with Docker, Kubernetes, and Consul for automatic service discovery. It handles Let's Encrypt certificates automatically and supports middleware for authentication, rate limiting, and headers. Ansible deploys Traefik as a system service or Docker container with full configuration management.
Deploy Traefik as System Service
---
- name: Deploy Traefik
hosts: proxy_servers
become: true
vars:
traefik_version: "3.1.0"
traefik_config_dir: /etc/traefik
traefik_acme_email: admin@example.com
traefik_dashboard_enabled: true
traefik_dashboard_users:
- "admin:{{ vault_traefik_dashboard_password | password_hash('bcrypt') }}"
tasks:
- name: Create Traefik user
ansible.builtin.user:
name: traefik
system: true
shell: /usr/sbin/nologin
- name: Download Traefik
ansible.builtin.get_url:
url: "https://github.com/traefik/traefik/releases/download/v{{ traefik_version }}/traefik_v{{ traefik_version }}_linux_amd64.tar.gz"
dest: /tmp/traefik.tar.gz
- name: Extract Traefik
ansible.builtin.unarchive:
src: /tmp/traefik.tar.gz
dest: /usr/local/bin/
remote_src: true
mode: '0755'
include: [traefik]
- name: Create directories
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: traefik
mode: '0750'
loop:
- "{{ traefik_config_dir }}"
- "{{ traefik_config_dir }}/dynamic"
- /var/lib/traefik
- name: Deploy static config
ansible.builtin.template:
src: traefik.yml.j2
dest: "{{ traefik_config_dir }}/traefik.yml"
owner: traefik
mode: '0640'
notify: restart traefik
- name: Deploy dynamic config
ansible.builtin.template:
src: dynamic.yml.j2
dest: "{{ traefik_config_dir }}/dynamic/config.yml"
owner: traefik
mode: '0640'
- name: Allow bind to ports 80/443
ansible.builtin.command: setcap 'cap_net_bind_service=+ep' /usr/local/bin/traefik
changed_when: true
- name: Create systemd service
ansible.builtin.copy:
dest: /etc/systemd/system/traefik.service
content: |
[Unit]
Description=Traefik
After=network-online.target
[Service]
User=traefik
ExecStart=/usr/local/bin/traefik --configFile={{ traefik_config_dir }}/traefik.yml
Restart=on-failure
RestartSec=5
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
mode: '0644'
notify:
- daemon reload
- restart traefik
- name: Start Traefik
ansible.builtin.service:
name: traefik
state: started
enabled: true
handlers:
- name: daemon reload
ansible.builtin.systemd:
daemon_reload: true
- name: restart traefik
ansible.builtin.service:
name: traefik
state: restarted
Static Config
# templates/traefik.yml.j2
api:
{% if traefik_dashboard_enabled %}
dashboard: true
insecure: false
{% endif %}
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
address: ":443"
http:
tls:
certResolver: letsencrypt
certificatesResolvers:
letsencrypt:
acme:
email: {{ traefik_acme_email }}
storage: /var/lib/traefik/acme.json
httpChallenge:
entryPoint: web
providers:
file:
directory: {{ traefik_config_dir }}/dynamic
watch: true
{% if traefik_docker_enabled | default(false) %}
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
{% endif %}
log:
level: INFO
accessLog:
filePath: /var/log/traefik/access.log
metrics:
prometheus:
entryPoint: websecure
Dynamic Config
# templates/dynamic.yml.j2
http:
routers:
{% if traefik_dashboard_enabled %}
dashboard:
rule: "Host(`traefik.{{ traefik_domain }}`)"
service: api@internal
middlewares:
- auth
tls:
certResolver: letsencrypt
{% endif %}
{% for service in traefik_services | default([]) %}
{{ service.name }}:
rule: "Host(`{{ service.domain }}`)"
service: {{ service.name }}
tls:
certResolver: letsencrypt
{% if service.middlewares is defined %}
middlewares:
{% for mw in service.middlewares %}
- {{ mw }}
{% endfor %}
{% endif %}
{% endfor %}
services:
{% for service in traefik_services | default([]) %}
{{ service.name }}:
loadBalancer:
servers:
{% for server in service.backends %}
- url: "{{ server }}"
{% endfor %}
{% if service.health_check is defined %}
healthCheck:
path: {{ service.health_check }}
interval: "10s"
{% endif %}
{% endfor %}
middlewares:
auth:
basicAuth:
users:
{% for user in traefik_dashboard_users %}
- "{{ user }}"
{% endfor %}
rate-limit:
rateLimit:
average: 100
burst: 200
security-headers:
headers:
stsSeconds: 63072000
stsIncludeSubdomains: true
stsPreload: true
forceSTSHeader: true
contentTypeNosniff: true
browserXssFilter: true
frameDeny: true
Service Variables
traefik_domain: example.com
traefik_services:
- name: webapp
domain: app.example.com
backends:
- "http://10.0.1.10:8080"
- "http://10.0.1.11:8080"
health_check: /health
middlewares: [rate-limit, security-headers]
- name: api
domain: api.example.com
backends:
- "http://10.0.1.20:3000"
health_check: /healthz
middlewares: [rate-limit]
Traefik with Docker
- name: Deploy Traefik with Docker provider
community.docker.docker_container:
name: traefik
image: "traefik:v{{ traefik_version }}"
restart_policy: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- /etc/traefik:/etc/traefik:ro
- /var/lib/traefik:/var/lib/traefik
command:
- "--providers.docker=true"
- "--providers.docker.exposedByDefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.email={{ traefik_acme_email }}"
- "--certificatesresolvers.letsencrypt.acme.storage=/var/lib/traefik/acme.json"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
labels:
traefik.enable: "true"
traefik.http.routers.dashboard.rule: "Host(`traefik.{{ traefik_domain }}`)"
traefik.http.routers.dashboard.service: "api@internal"
Health Check
- name: Check Traefik health
ansible.builtin.uri:
url: "http://localhost:8080/ping"
status_code: 200
- name: Check Traefik API
ansible.builtin.uri:
url: "http://localhost:8080/api/http/routers"
return_content: true
register: routers
Troubleshooting
Certificate Not Issuing
- name: Check ACME storage
ansible.builtin.command: cat /var/lib/traefik/acme.json
register: acme_data
changed_when: false
no_log: true
Check Traefik Logs
- name: View recent logs
ansible.builtin.command: journalctl -u traefik --no-pager -n 100
register: traefik_logs
changed_when: false
Related Articles
Conclusion
Traefik is the cloud-native alternative to Nginx/HAProxy — it discovers services automatically from Docker labels or file-based config, handles TLS certificates via Let's Encrypt without any cron jobs, and provides middleware for security headers, rate limiting, and authentication. Ansible templates both static and dynamic configs, deploys as a system service or Docker container, and manages the full proxy lifecycle as code.