diff --git a/top.sls b/top.sls
index effb15ecc69f2fa8b42c2b34cee058b8d5771ed1..6f648164bf888ea384c33093fa19b34bde3b8d9c 100644
--- a/top.sls
+++ b/top.sls
@@ -42,6 +42,9 @@ base:
     - bird.ospf
     - kernel.sysctl
     - network
+  'roles:traefik':
+    - match: pillar
+    - traefik
   'roles:netbox':
     - match: pillar
     - netbox
diff --git a/traefik/files/traefik.toml.j2 b/traefik/files/traefik.toml.j2
new file mode 100644
index 0000000000000000000000000000000000000000..67bbd21bd0bc316bbab52a8bae2e93c08a31dd05
--- /dev/null
+++ b/traefik/files/traefik.toml.j2
@@ -0,0 +1,84 @@
+{% set traefik = pillar.get('traefik', {}) -%}
+{% set letsencrypt = pillar.get('letsencrypt', {}) -%}
+{% set letsencrypt_domains = traefik.get('letsencrypt', {}) -%}
+{% set backend = traefik.get('backend', {}) -%}
+{% set backend_auth = backend.get('auth', {}) -%}
+
+debug = {% if traefik.get('debug', false) %}true{% else %}false{% endif %}
+
+logLevel = "{{ traefik.get('log_level', 'ERROR') }}"
+defaultEntryPoints = ["https","http"]
+
+[entryPoints]
+  [entryPoints.http]
+    address = ":80"
+      [entryPoints.http.redirect]
+      entryPoint = "https"
+
+  [entryPoints.https]
+    address = ":443"
+    [entryPoints.https.tls]
+
+  [entryPoints.traefik]
+    address = "127.0.0.1:8880"
+
+    {% if backend_auth -%}
+    [entryPoints.traefik.auth]
+      [entryPoints.traefik.auth.basic]
+        users = [
+          {%- for user,pass in backend_auth.items()|sort %}
+          "{{ user }}:{{ pass }}",
+          {%- endfor %}
+        ]
+    {%- endif %}
+
+[file]
+[backends]
+  [backends.traefik-backend]
+    [backends.traefik-backend.servers.backend1]
+      url = "http://127.0.0.1:8880"
+
+[frontends]
+  [frontends.traefik-frontend]
+    entryPoints = ["https"]
+    backend = "traefik-backend"
+    [frontends.traefik-frontend.routes.traefik]
+      rule = "PathPrefixStrip:/traefik;Host:{{ backend.get('host', grains.fqdn ) }}"
+
+[retry]
+
+[docker]
+  endpoint = "unix:///var/run/docker.sock"
+  domain = "{{ traefik.get('default-domain', grains.get('domain', grains.fqdn)) }}"
+  watch = true
+  exposedByDefault = false
+
+{% if letsencrypt.get('email') and letsencrypt.get('agree-tos', false) and letsencrypt_domains -%}
+[acme]
+  email = "{{ letsencrypt.email }}"
+  storage = "acme.json"
+  entryPoint = "https"
+  onHostRule = false
+  acmeLogging = {% if traefik.get('debug', false) %}true{% else %}false{% endif %}
+  {% if letsencrypt.get('environment', 'production') != 'staging' -%}
+  caServer = "https://acme-v02.api.letsencrypt.org/directory"
+  {%- else %}
+  caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
+  {%- endif %}
+  [acme.httpChallenge]
+    entryPoint = "http"
+  {% for domain, sans in letsencrypt_domains.items() %}
+  [[acme.domains]]
+    main = "{{ domain }}"
+    {% if sans -%}
+    sans = {{ sans }}
+    {%- endif %}
+  {%- endfor %}
+{%- endif %}
+
+[api]
+  entryPoint = "traefik"
+  [api.statistics]
+
+[metrics.prometheus]
+  entryPoint = "traefik"
diff --git a/traefik/init.sls b/traefik/init.sls
new file mode 100644
index 0000000000000000000000000000000000000000..36334df4b9d3e0b9bbb7629282e235d8688c4f8b
--- /dev/null
+++ b/traefik/init.sls
@@ -0,0 +1,70 @@
+{% set docker = pillar.get('docker', {}) %}
+{% set traefik = pillar.get('traefik', {}) %}
+{% set backend = traefik.get('backend', {}) %}
+{% set volume = docker.volume_root | default('/srv/docker') ~ '/traefik' %}
+{% set container = traefik.get('container', 'traefik') %}
+{% set version = traefik.get('container', 'latest') %}
+
+include:
+  - docker
+
+docker-volume-traefik:
+  file.directory:
+    - name: {{ volume }}
+    - makedirs: True
+    - require:
+      - file: docker-volume-root
+
+{{ volume }}/traefik.toml:
+  file.managed:
+    - user: root
+    - group: root
+    - mode: 644
+    - template: jinja
+    - source: salt://traefik/files/traefik.toml.j2
+    - require:
+      - file: docker-volume-traefik
+
+{{ volume }}/acme.json:
+  file.managed:
+    - user: www-data
+    - group: www-data
+    - mode: 600
+    - replace: false
+    - require:
+      - file: docker-volume-traefik
+
+docker-image-traefik:latest:
+  docker_image.present:
+    - name: traefik:latest
+    - force: true
+    - watch:
+      - pkg: docker
+    - require:
+      - file: docker-volume-root
+
+docker-container-traefik:
+  docker_container.running:
+    - image: traefik:latest
+    - name: traefik
+    - command: --docker
+    - restart_policy: always
+    - port_bindings:
+      - "80:80"
+      - "443:443"
+    - binds:
+      - /var/run/docker.sock:/var/run/docker.sock
+      - {{ volume }}/traefik.toml:/traefik.toml
+      - {{ volume }}/acme.json:/acme.json
+    - require: &traefik-require
+      - pkg: docker
+      - file: {{ volume }}/traefik.toml
+      - file: {{ volume }}/acme.json
+    - watch: *traefik-require
+
+grains-traefik-metrics-host:
+  grains.present:
+    - name: traefik-metrics-host
+    - value: {{ backend.get('host', grains.fqdn) }}
+    - require:
+      - docker_container: docker-container-traefik