Docker. Create secure web service using Let’s Encrypt with Traefik
In my previous post I speak about Traefik concepts and designs. I explain the main words used in Traefik as Endpoint, Router, Rule and etc. So in here I will concentrate only on practice.
What We’ll Do
- We’ll use a pre-made container —
containous/whoami
— capable of telling you where it is hosted and what it receives when you call it. - We’ll deploy that container through traefik proxy using docker-compose.
- We’ll define a dashboard that shows us all deployed services.
- We’ll setup letsencrypt configures that will automatically get and update free SSL certificates.
- We’ll configure proxy to redirect all insecure requests to https scheme.
Prerequisite
You have installed a Docker and docker-compose. We will use Traefik 2.1
Repository
All presented compose files has stored in this Github Repository.
1. Use traefik as proxy
This basic configuration will be a start point to run traefik with docker.
version: '3'
services:
traefik:
image: traefik:v2.1
container_name: traefik
command:
# Enable dashboard
- --api.dashboard=true
# Allow use API and dashboard though insecure
- --api.insecure=true
# Use docker as provider
- --providers.docker=true
# Enable log income requsts
- --accesslog=true
# Set log level (default ERROR)
- --log.level=ERROR
# Define entrypoint that listens 80 port
- --entryPoints.web.address=:80
ports:
# The HTTP port
- "80:80"
# The Web UI (enabled by --api.insecure=true)
- "8080:8080"
volumes:
# Mount docker socker from host machine
- /var/run/docker.sock:/var/run/docker.sock:ro
whoami:
# A container that exposes an API to show its IP address
image: containous/whoami
container_name: whoami
labels:
# Create a route `whoami` and bound with defined entrypoint
- traefik.http.routers.whoami.entrypoints=web
# Create rule
- traefik.http.routers.whoami.rule=Host(`whoami.example.com`)
We declare two services. First service is our traefik. Second one is a service response information about itself on web requests. Also we’ve enabled API along with the dashboard. And we can see it on localhost:8080/dashboard/.
After we append a hostname whoami.example.com
to local DNS (i.e. to /etc/hosts
) we can access to a created whoami
container from browser by link http://whoami.example.com.
Without any changing DNS service we also could send request to whoami
with curl:
# curl -H Host:whoami.example.com http://localhost
Hostname: 3b64e9ee3e38
IP: 127.0.0.1
IP: 172.28.0.2
RemoteAddr: 172.28.0.3:55870
GET / HTTP/1.1
Host: whoami.example.com
User-Agent: curl/7.54.0
Accept: */*
Accept-Encoding: gzip
X-Forwarded-For: 172.28.0.1
X-Forwarded-Host: whoami.example.com
X-Forwarded-Port: 80
X-Forwarded-Proto: http
X-Forwarded-Server: a9dfe96d5877
X-Real-Ip: 172.28.0.1
2. HTTPS with Let’s Encrypt
Today almost all services are accessed by secure https connection. Installing an SSL certificate is the most common work. It also could be done with Traefik. Let’s create a compose file with next content:
version: '3'
services:
traefik:
image: traefik:v2.1
container_name: traefik
command:
- --api.dashboard=true
- --api.insecure=true
- --providers.docker=true
- --accesslog=true
- --entryPoints.web.address=:80
- --entryPoints.websecure.address=:443
# Enable ACME (Let's Encrypt): automatic SSL.
- --certificatesResolvers.letsencrypt.acme.email=user@example.com
# File or key used for certificates storage.
- --certificatesResolvers.letsencrypt.acme.storage=/acme/acme.json
# Uncomment the line to use Let's Encrypt's staging server,
- --certificatesResolvers.letsencrypt.acme.caServer=https://acme-staging-v02.api.letsencrypt.org/directory
# Use a TLS-ALPN-01 ACME challenge.
# - --certificatesResolvers.letsencrypt.acme.tlsChallenge=true
# Use a HTTP-01 ACME challenge.
- --certificatesResolvers.letsencrypt.acme.httpChallenge=true
# EntryPoint to use for the HTTP-01 challenges.
- --certificatesResolvers.letsencrypt.acme.httpChallenge.entryPoint=web
ports:
# The HTTP port
- "80:80"
# The HTTPS port
- "443:443"
# The Web UI (enabled by --api.insecure=true)
- "8080:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
# ACME certificates can be stored in a JSON file that needs to have a 600 file mode
- ./acme:/acme
whoami:
# A container that exposes an API to show its IP address
image: containous/whoami
container_name: whoami
labels:
# Handle secure and insecure traffic from websecure,web entry points
- traefik.http.routers.whoami.entrypoints=websecure,web
# Accept requests that matched specific Host
- traefik.http.routers.whoami.rule=Host(`whoami.example.com`)
# Enable TLS/SSL
- traefik.http.routers.whoami.tls=true
# Bind with created certresolver `letsencrypt`
- traefik.http.routers.whoami.tls.certresolver=letsencrypt
A traefik service will call Let’s encrypt HTTP challenge to create a free SSL certificate.
The server have to be explored from Internet by 80 port and specified DNS whoami.example.com
.
In this example to test purposes was setup a staging letsencrypt server. Comment the line with acme.caServer
to use production server by default. Also don’t forget to change a mail address user@example.com
to your.
3. Add BasicAuth to Dashboard
Once Traefik has found a match for the request, it can process it before forwarding it to the service.
In the following example, we’ll add a BasicAuth mechanism for our route.
This is done with a few additional labels on traefik
service. After that you could open Dashboard by name dashboard.example.com
.
version: '3'
services:
traefik:
image: traefik:v2.1
container_name: traefik
command:
- --api=true
- --api.dashboard=true
- --providers.docker=true
- --providers.docker.exposedbydefault=true
- --accesslog=true
- --entryPoints.web.address=:80
- --entryPoints.websecure.address=:443
- --certificatesResolvers.letsencrypt.acme.email=user@example.com
- --certificatesResolvers.letsencrypt.acme.storage=/acme/acme.json
# Uncomment the line to use Let's Encrypt's staging server,
- --certificatesResolvers.letsencrypt.acme.caServer=https://acme-staging-v02.api.letsencrypt.org/directory
- --certificatesResolvers.letsencrypt.acme.httpChallenge=true
- --certificatesResolvers.letsencrypt.acme.httpChallenge.entryPoint=web
labels:
- traefik.http.routers.api.entrypoints=websecure,web
- traefik.http.routers.api.rule=Host(`dashboard.example.com`)
- traefik.http.routers.api.tls=true
- traefik.http.routers.api.tls.certresolver=letsencrypt
# Connect the router `api` to internal service 'api'
- traefik.http.routers.api.service=api@internal
# Declaring a middleware with name `auth`
# Declaring the user list - echo $(htpasswd -nb admin password) | sed -e s/\\$/\\$\\$/g
- "traefik.http.middlewares.auth.basicauth.users=admin:$$apr1$$mW/l73Bf$$WsprkCzl5.QbLdY9c4kdB0"
# Referencing an `auth` middleware
- traefik.http.routers.api.middlewares=auth
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./acme:/acme
whoami:
# A container that exposes an API to show its IP address
image: containous/whoami
container_name: whoami
labels:
- traefik.http.routers.whoami.entrypoints=websecure,web
- traefik.http.routers.whoami.rule=Host(`whoami.example.com`)
- traefik.http.routers.whoami.tls=true
- traefik.http.routers.whoami.tls.certresolver=letsencrypt
Let’s explain what we made upper. First, we remove the insecure api (specifying --api
instead of --api.insecure
).
We declare a tls connection to api
router that we made in previous part.
After that we bound the router with internal api
service. Declaring a Middleware with name auth
(traefik.http.middlewares.auth.basicauth.users
). The value of it was include a string in format username:xxxx
.
It could be generated with shell command - echo $(htpasswd -nb username password) | sed -e s/\\$/\\$\\$/g
.
And at the end, join the middleware to the router api
(traefik.http.routers.api.middlewares=auth
).
4. HTTPS Redirection
Now that we have HTTPS routes, let’s redirect every non-https requests to their https equivalent.
For that, we’ll reuse the previous trick and add just 3 labels to declare a redirect middleware. RedirectScheme will help us. It redirects request from a scheme to another. We will catch requests only for specific domain - whoami.example.com
. See the example below:
version: '3'
services:
traefik:
image: traefik:v2.1
container_name: traefik
command:
- --api=true
- --api.dashboard=true
- --providers.docker=true
- --providers.docker.exposedbydefault=true
- --accesslog=true
- --entryPoints.web.address=:80
- --entryPoints.websecure.address=:443
- --certificatesResolvers.myresolver.acme.email=r.gainanov@skoltech.ru
- --certificatesResolvers.myresolver.acme.storage=/acme/acme.json
# Uncomment the line to use Let's Encrypt's staging server,
- --certificatesResolvers.myresolver.acme.caServer=https://acme-staging-v02.api.letsencrypt.org/directory
- --certificatesResolvers.myresolver.acme.httpChallenge=true
- --certificatesResolvers.myresolver.acme.httpChallenge.entryPoint=web
labels:
- traefik.http.routers.api.entrypoints=websecure,web
- traefik.http.routers.api.rule=Host(`dashboard.example.com`)
- traefik.http.routers.api.tls=true
- traefik.http.routers.api.tls.certresolver=letsencrypt
- traefik.http.routers.api.service=api@internal
- traefik.http.routers.api.middlewares=auth
- "traefik.http.middlewares.auth.basicauth.users=admin:$$apr1$$mW/l73Bf$$WsprkCzl5.QbLdY9c4kdB0"
# Declaring a middleware with name `https_redirect` uses Redirecting the Client to a `https` Scheme
- traefik.http.middlewares.https_redirect.redirectscheme.scheme=https
# Set the permanent option to true to apply a permanent redirection.
- traefik.http.middlewares.https_redirect.redirectscheme.permanent=true
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./acme:/acme
whoami:
# A container that exposes an API to show its IP address
image: containous/whoami
container_name: whoami
labels:
traefik.http.routers.app.rule: Host(`whoami.example.com`)
traefik.http.routers.app.entrypoints: web
# Set the middleware `https_redirect` to `app` router to apply a redirection to https.
traefik.http.routers.app.middlewares: https_redirect
traefik.http.routers.appsecured.rule: Host(`whoami.example.com`)
traefik.http.routers.appsecured.entrypoints: websecure
traefik.http.routers.appsecured.tls: true
traefik.http.routers.appsecured.tls.certresolver: myresolver
We use the previous example and add middlewares.https_redirect
as traefik service label. After we bind this middleware to a router defined in whoami - traefik.http.routers.app.middlewares: https_redirect
.
Follow to next advice if want a global redirect rule for requests to all insecured hosts. Move 2 rows from whoami
service into traefik label’s section .app.rule
, .app.entrypoints
and .app.middlewares
. And change the value of the Rule .rule: Host(`whoami.example.com`)
to HostRegexp(`{host:.+}`)
Conclusion
Hopefully, I’ve gone through important questions you’ll have when dealing with Traefik 2.0 in a Docker setup, and I hope this examples get you a start point to explore more complex configurations.
Additional information
- Official Documentation - the start point to know more about Traefik
- Basic Example - Traefik - docker-compose basic example by Traefik
- Docker Configuration Reference - configure Traefik with Docker Labels
- Traefik 2.0 & Docker 101 - step by step with Traefik in an author blog