<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>The Ops Community ⚙️: Michał Kurzeja</title>
    <description>The latest articles on The Ops Community ⚙️ by Michał Kurzeja (@mkurzeja).</description>
    <link>https://community.ops.io/mkurzeja</link>
    <image>
      <url>https://community.ops.io/images/y4Nj3NHv3sZNRPrKBv2m3qixY6MLx9xwx2s_0_rLzj8/rs:fill:90:90/g:sm/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkub3BzLmlv/L3JlbW90ZWltYWdl/cy91cGxvYWRzL3Vz/ZXIvcHJvZmlsZV9p/bWFnZS8xOTIvZjBk/YjliYzQtZWQyNC00/NjYzLTgxYWQtNmU1/MzhiZGQ1MzEyLmpw/Zw</url>
      <title>The Ops Community ⚙️: Michał Kurzeja</title>
      <link>https://community.ops.io/mkurzeja</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://community.ops.io/feed/mkurzeja"/>
    <language>en</language>
    <item>
      <title>Docker reverse proxy using Traefik</title>
      <dc:creator>Michał Kurzeja</dc:creator>
      <pubDate>Thu, 26 May 2022 05:19:28 +0000</pubDate>
      <link>https://community.ops.io/mkurzeja/docker-reverse-proxy-using-traefik-2l04</link>
      <guid>https://community.ops.io/mkurzeja/docker-reverse-proxy-using-traefik-2l04</guid>
      <description>&lt;h2&gt;
  
  
  Why you might need a reverse proxy server?
&lt;/h2&gt;

&lt;p&gt;The need of introducing a reverse proxy to a docker/docker-compose config is quite popular. Some common use-cases are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;routing inbound traffic to the right container in multi-container environments (heavily used by me in PHP refactoring projects using the strangler pattern), ex. route the request to the right web servers&lt;/li&gt;
&lt;li&gt;terminate SSL (ideally using Let's encrypt?)&lt;/li&gt;
&lt;li&gt;allow for load balancing in multiple backend servers environments&lt;/li&gt;
&lt;li&gt;basic auth&lt;/li&gt;
&lt;li&gt;IP whitelist/blacklist&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As you can see on the image below, an example reverse proxy sits in front of your application and can terminate the SSL, and then route the client requests to the correct backend web servers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.ops.io/images/slvXyn4hlhN5f7CLZpz8R42YgzOopU1YkB-r9jqm894/w:880/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkub3BzLmlv/L3JlbW90ZWltYWdl/cy91cGxvYWRzL2Fy/dGljbGVzL3ZwZGk3/NDN2OGpwdWIyNHJt/YTVvLnBuZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/slvXyn4hlhN5f7CLZpz8R42YgzOopU1YkB-r9jqm894/w:880/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkub3BzLmlv/L3JlbW90ZWltYWdl/cy91cGxvYWRzL2Fy/dGljbGVzL3ZwZGk3/NDN2OGpwdWIyNHJt/YTVvLnBuZw" alt="Reverse proxy example" width="880" height="503"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Using reverse proxy, you can also split incoming traffic onto multiple servers, all working inside an internal network and exposed under a single public ip address.&lt;/p&gt;

&lt;p&gt;An interesting fact is that a good reverse proxy can also protect you from hacker requests, by example by filtering out malicious HTTP requests - like the recent log4j vulnerability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common approach to reverse proxy servers in Docker
&lt;/h2&gt;

&lt;p&gt;There is a popular solution that is using &lt;a href="https://hub.docker.com/r/jwilder/nginx-proxy"&gt;NGINX&lt;/a&gt; as the reverse proxy server. It is configured using labels, and thus quite easy to implement. In fact, I have used it for the last years quite often.&lt;/p&gt;

&lt;p&gt;The problem with it was when I had to add some more sugar to it, like SSL, basic auth or some compression. This is supported by the mentioned nginx-proxy, but a bit hard to configure in some cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  A better approach? Traefik!
&lt;/h2&gt;

&lt;p&gt;A couple of months ago, I had (again) a need for a reverse proxy server in one of our new projects. I was setting up a review-apps environment, and needed something efficient, but also stable. In most cases I would reuse the mentioned NGINX setup, but I hit some issues with it in the past, mostly connected with no debug possibilities, so I decided to give &lt;a href="https://traefik.io/"&gt;Traefik&lt;/a&gt; a try.&lt;/p&gt;

&lt;p&gt;Do you know the feeling when you discover a new thing and after a week or two you already wonder how you could have lived without it? That was the case for me with Traefik.&lt;/p&gt;

&lt;p&gt;Simplifying a bit, Traefik is built of four main concepts: entrypoint, router, middleware and service. You can configure it to listen on certain entrypoints (ports, example TCP/HTTP on port 80), then the incoming request is matched to a route, each request can go through middleware (or a couple of them) - ex. path rewrite, compression, etc.; to finally reach a configured service that was assigned to the matched route. Services will usually map to your app web server.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.ops.io/images/XITUv_xPyvK5FyQ7tfssQLAxNd72hUinujg6EAehdFQ/w:880/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkub3BzLmlv/L3JlbW90ZWltYWdl/cy91cGxvYWRzL2Fy/dGljbGVzLzJlY21q/a3hmZW12NDNjaTV4/bGI4LnBuZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/XITUv_xPyvK5FyQ7tfssQLAxNd72hUinujg6EAehdFQ/w:880/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkub3BzLmlv/L3JlbW90ZWltYWdl/cy91cGxvYWRzL2Fy/dGljbGVzLzJlY21q/a3hmZW12NDNjaTV4/bGI4LnBuZw" alt="Traefik components overview" width="880" height="503"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic reverse proxy set-up
&lt;/h2&gt;

&lt;p&gt;Traefik supports multiple different configuration providers, including files or even HTTP endpoints, but we will go with the one that works best for me - Docker. It's using the same approach of labels as nginx-proxy, but has a bit more configuration possibilities.&lt;/p&gt;

&lt;p&gt;Let's have a look at an example config that is using docker-compose and is available in Traefik official documentation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: "3.3"

services:
  traefik:
    image: "traefik:v2.6"
    command:
      #- "--log.level=DEBUG"
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
    ports:
      - "80:80"
      - "8080:8080"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"

  whoami:
    ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--api.insecure=true&lt;/code&gt; - allows accessing a Traefik dashboard - that simplifies debugging, but should be disabled outside of development environments due to security reasons.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--providers.docker=true&lt;/code&gt; - enables the Docker configuration discovery&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--providers.docker.exposedbydefault=false&lt;/code&gt; - do not expose Docker services by default&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--entrypoints.web.address=:80&lt;/code&gt; - create an entrypoint called &lt;code&gt;web&lt;/code&gt;, listening on &lt;code&gt;:80&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We expose port 80 to allow access to the &lt;code&gt;web&lt;/code&gt; entrypoint, and port 8080 as it is the default dashboard port. We also need to connect a volume with the docker.sock so Traefik can talk with the Docker daemon (and fetch information about running containers).&lt;/p&gt;

&lt;p&gt;Now, let's have a look at an example service we would like to expose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: "3.3"

services:
  traefik:
    ...
  whoami:
    image: "traefik/whoami"
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami.rule=Host(`whoami.localhost`)"
      - "traefik.http.routers.whoami.entrypoints=web"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We don't need to expose the port, the only thing required to expose a service is to add a couple of labels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;traefik.enable=true&lt;/code&gt; - tell Traefik this is something we would like to expose&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;traefik.http.routers.whoami.rule=Host("whoami.localhost")&lt;/code&gt; - specify the rule used to match a request to this service. The &lt;code&gt;whoami&lt;/code&gt; part is a name that you can specify, you can also adjust the Host to your needs. Traefik also supports other matchers, f.e. path, but we will take a look at them a bit later.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;traefik.http.routers.whoami.entrypoints=web&lt;/code&gt; - what entrypoint should be used for the &lt;code&gt;whoami&lt;/code&gt; service.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can specify multiple routers for each container, just alter the router name. Make also sure the router names are unique and you have no collissions where two containers specify the same router name.&lt;/p&gt;

&lt;p&gt;My final test file looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: "3.3"

services:
  traefik:
    image: "traefik:v2.6"
    command:
      #- "--log.level=DEBUG"
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
    ports:
      - "80:80"
      - "8080:8080"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"

  whoami:
    image: "traefik/whoami"
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami.rule=Host(`localhost`)"
      - "traefik.http.routers.whoami.entrypoints=web"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the changed hostname on line 22.&lt;/p&gt;

&lt;p&gt;Let's run this: &lt;code&gt;docker-compose up -d&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;After pulling the images, the service is exposed under localhost:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.ops.io/images/YfMP4ulxm55r_ctF3_swrSm8_UuU_78ORbEkz5Ci5IU/w:880/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkub3BzLmlv/L3JlbW90ZWltYWdl/cy91cGxvYWRzL2Fy/dGljbGVzLzdheWtj/ejNkaHQ1a211M2Rz/cW1tLnBuZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/YfMP4ulxm55r_ctF3_swrSm8_UuU_78ORbEkz5Ci5IU/w:880/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkub3BzLmlv/L3JlbW90ZWltYWdl/cy91cGxvYWRzL2Fy/dGljbGVzLzdheWtj/ejNkaHQ1a211M2Rz/cW1tLnBuZw" alt="Proxied service" width="864" height="548"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I can also open localhost:8080 to check the current Traefik configuration:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.ops.io/images/zTSEqkvW6He4LABbEEkXrfvPTHqOE9wy-ES2Yv_fcB0/w:880/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkub3BzLmlv/L3JlbW90ZWltYWdl/cy91cGxvYWRzL2Fy/dGljbGVzL2YyYTBk/OGF3ZGVxYnphYWoy/aGZsLnBuZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/zTSEqkvW6He4LABbEEkXrfvPTHqOE9wy-ES2Yv_fcB0/w:880/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkub3BzLmlv/L3JlbW90ZWltYWdl/cy91cGxvYWRzL2Fy/dGljbGVzL2YyYTBk/OGF3ZGVxYnphYWoy/aGZsLnBuZw" alt="Traefik dashboard" width="880" height="531"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.ops.io/images/FxZ7cRRsh8IN2DgmYlNA2iMb63PccOJ0Fq-AgQbt-wQ/w:880/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkub3BzLmlv/L3JlbW90ZWltYWdl/cy91cGxvYWRzL2Fy/dGljbGVzL3d5Mmoy/MWhxcnp6ZGZod3N0/cW1xLnBuZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/FxZ7cRRsh8IN2DgmYlNA2iMb63PccOJ0Fq-AgQbt-wQ/w:880/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkub3BzLmlv/L3JlbW90ZWltYWdl/cy91cGxvYWRzL2Fy/dGljbGVzL3d5Mmoy/MWhxcnp6ZGZod3N0/cW1xLnBuZw" alt="Traefik HTTP route view" width="880" height="628"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Load balancing
&lt;/h2&gt;

&lt;p&gt;Now here comes the fun part. You already have load balancing in place! If you scale the whoami service in docker-compose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: "3.3"

services:
  traefik:
    ...
  whoami:
    image: "traefik/whoami"
    scale: 5
    labels:
      ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then Traefik will connect all the containers to the service:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.ops.io/images/soIfZFAeaLREMSaYcVbhToWFqcK2-iNQL-dyIg0Tt5I/w:880/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkub3BzLmlv/L3JlbW90ZWltYWdl/cy91cGxvYWRzL2Fy/dGljbGVzL3U3NXhk/cDV4NGVyZGJyang2/d2Y2LnBuZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/soIfZFAeaLREMSaYcVbhToWFqcK2-iNQL-dyIg0Tt5I/w:880/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkub3BzLmlv/L3JlbW90ZWltYWdl/cy91cGxvYWRzL2Fy/dGljbGVzL3U3NXhk/cDV4NGVyZGJyang2/d2Y2LnBuZw" alt="Traefik dashboard load balancer view" width="880" height="484"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And divide the traffic evenly. That's it! You have a fully working load balancer&lt;/p&gt;

&lt;h2&gt;
  
  
  Multiple services and path matching
&lt;/h2&gt;

&lt;p&gt;As you can imagine, adding more services to the reverse proxy is quite easy. We can make them use different domains - by providing a different domain inside &lt;code&gt;Host("localhost")&lt;/code&gt;. But we can also route the services by path. Let's add a new service to the docker-compose.yml file we created previously:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
  whoami2:
    image: "nginxdemos/hello"
    scale: 1
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami2.rule=Host(`localhost`) &amp;amp;&amp;amp; Path(`/whoami2`)"
      - "traefik.http.routers.whoami2.entrypoints=web"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice three changes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I have used a different image, so something else is served through HTTP.&lt;/li&gt;
&lt;li&gt;I have switched the name in the routers part to &lt;code&gt;whoami2&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;I have added &lt;code&gt;&amp;amp;&amp;amp; Path("/whoami2")&lt;/code&gt; to routing rule. So now both the hostname has to match &lt;code&gt;localhost&lt;/code&gt; and the path needs to be exactly &lt;code&gt;whoami2&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's check how this works:&lt;br&gt;
&lt;a href="https://community.ops.io/images/psZ3ZYT8OOjOamfIXmEcaYnLfcEe2DuNhRxT25ru1gg/w:880/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkub3BzLmlv/L3JlbW90ZWltYWdl/cy91cGxvYWRzL2Fy/dGljbGVzL2NyZGNy/Nnh1d28wa3Z4eTFv/a3ExLnBuZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/psZ3ZYT8OOjOamfIXmEcaYnLfcEe2DuNhRxT25ru1gg/w:880/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkub3BzLmlv/L3JlbW90ZWltYWdl/cy91cGxvYWRzL2Fy/dGljbGVzL2NyZGNy/Nnh1d28wa3Z4eTFv/a3ExLnBuZw" alt="Traefik path routing example" width="880" height="517"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you need to match a path prefix, not only an exact match, you can use &lt;code&gt;PathPrefix("/whoami2")&lt;/code&gt; instead.&lt;/p&gt;
&lt;h2&gt;
  
  
  SSL encryption
&lt;/h2&gt;

&lt;p&gt;I also quite often need a thin layer between my app and the client, that could take the SSL certificate creation and updates of my shoulder. Letsencrypt did a great job, but when the task is to quickly expose a Docker service it required some tricks.&lt;/p&gt;

&lt;p&gt;Traefik has built in certificate resolvers. How does this work? Well, you just specify what resolver/provider you would like to use, and then it will handle all the required certificates. So if I have a running instance of Traefik and add a domain like &lt;code&gt;accesto.com&lt;/code&gt; for one of the containers - it will automatically call the resolver. In the example case, the resolver is Let's Encrypt, and it will fetch the certificate for me.&lt;/p&gt;

&lt;p&gt;Lets have a look at a slightly modified example we started with - the one with one exposed Docker container.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: "3.3"

services:
  traefik:
    image: "traefik:v2.6"
    command:
      #- "--log.level=DEBUG"
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443" # new
      - "--certificatesresolvers.myresolver.acme.tlschallenge=true" # new
      - "--certificatesresolvers.myresolver.acme.email=your@email.com" # new
      - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json" # new
    ports:
      - "80:80"
      - "443:443" # new
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "./letsencrypt:/letsencrypt" # new
(...)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let me start with the changes added to Traefik labels. There are 4 new lines [12-15]&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Line 12 - we need to add a new SSL entrypoint, listening to the HTTPS 443 port&lt;/li&gt;
&lt;li&gt;Line 13 - enable the acme tls challenge for &lt;code&gt;myresolver&lt;/code&gt; certificate resolver&lt;/li&gt;
&lt;li&gt;Line 14 - provide e-mail used by let's encrypt to send important information&lt;/li&gt;
&lt;li&gt;Line 15 - path to a JSON file where the certificates will be stored&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then some additional changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Line 18 - expose the 443 port&lt;/li&gt;
&lt;li&gt;Line 21 - link the file where certificates are stored to a file on local disk, so updating Traefik does not require fetching all certificates again&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are some changes, but I believe all are quite simple and should be easy to understand. Now let's look at what's changed on the service level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(...)
  whoami:
    image: "traefik/whoami"
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami.rule=Host(`mydomain.com`)"
      - "traefik.http.routers.whoami.entrypoints=web,websecure" # changed
      - "traefik.http.routers.whoami.tls.certresolver=myresolver" # new
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Line 29 - we added the &lt;code&gt;websecure&lt;/code&gt; entrypoint, so our service is not available both via HTTP and HTTPS&lt;/li&gt;
&lt;li&gt;Line 30 - we notified Traefik, it should use &lt;code&gt;myresolver&lt;/code&gt; to get the SSL certificate for this service&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And that's actually it. If you now run &lt;code&gt;docker-compose up -d&lt;/code&gt; Traefik will automatically fetch the certificate and use it.&lt;/p&gt;

&lt;p&gt;You can also check the Traefik dashboard to see the SSL status for a router:&lt;br&gt;
&lt;a href="https://community.ops.io/images/TdE9WiMpbrf7bksHz5sP8tIpCiIggGm1Am9IeX83LQA/w:880/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkub3BzLmlv/L3JlbW90ZWltYWdl/cy91cGxvYWRzL2Fy/dGljbGVzLzYybTNr/c2pvMDhybXExZjZt/cDdrLnBuZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/TdE9WiMpbrf7bksHz5sP8tIpCiIggGm1Am9IeX83LQA/w:880/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkub3BzLmlv/L3JlbW90ZWltYWdl/cy91cGxvYWRzL2Fy/dGljbGVzLzYybTNr/c2pvMDhybXExZjZt/cDdrLnBuZw" alt="Traefik SSL status in dashboard" width="880" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And that's actually everything you need to do in order to have a &lt;strong&gt;reverse proxy in Docker with SSL termination&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Middleware features
&lt;/h2&gt;

&lt;p&gt;Following the previous steps you have a fully working reverse proxy, with in-built load balancer and SSL encryption. Nice right? But in my case, I quite often need to secure access to such endpoints. F.e. whitelist incoming IPs or require a username and password. This could obviously be done in the exposed application, but I think that a reverse proxy is a better place for this.&lt;/p&gt;
&lt;h3&gt;
  
  
  IP Whitelist
&lt;/h3&gt;

&lt;p&gt;Web application security is key, so how can you add an IP whitelist? It's just two new labels:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(...)
  whoami:
    image: "traefik/whoami"
    labels:
    (...)
        - "traefik.http.middlewares.whoami-filter-ip.ipwhitelist.sourcerange=192.168.1.1/24,127.0.0.1/32"
        - "traefik.http.routers.whoami.middlewares=whoami-filter-ip"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case, &lt;code&gt;whoami-filter-ip&lt;/code&gt; is our middleware name, it has to be unique. The provided IP list will be allowed to access your service, other sources will get a &lt;code&gt;403 Forbidden&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Basic auth
&lt;/h3&gt;

&lt;p&gt;Quite frequently, we need to secure websites by adding a basic auth in front of it. Adding basic auth is also pretty simple and uses the same approach of middleware.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(...)
  whoami:
    image: "traefik/whoami"
    labels:
    (...)
      - "traefik.http.middlewares.whoami-auth.basicauth.users=test:$$apr1$$ra8uoeq5$$HqiATqC5edVVEXznsNiVV/,test2:$$apr1$$8ol2akty$$BW.Fsa.K3tc1DzcJ6l9ql1"
      - "traefik.http.routers.whoami.middlewares=whoami-auth"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To create a user and password pair, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo $(htpasswd -nb user password) | sed -e s/\\$/\\$\\$/g
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you open the page now, it will ask for credentials:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.ops.io/images/fdjHr4Np8BIN7k__gxcwGI82OVqBYGK2IuMOT9b6x6w/w:880/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkub3BzLmlv/L3JlbW90ZWltYWdl/cy91cGxvYWRzL2Fy/dGljbGVzL2dxNWJo/cml5Y3hkaWl3b3lr/bmJ6LnBuZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/fdjHr4Np8BIN7k__gxcwGI82OVqBYGK2IuMOT9b6x6w/w:880/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkub3BzLmlv/L3JlbW90ZWltYWdl/cy91cGxvYWRzL2Fy/dGljbGVzL2dxNWJo/cml5Y3hkaWl3b3lr/bmJ6LnBuZw" alt="Basic auth example" width="880" height="549"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Please note the &lt;code&gt;sed&lt;/code&gt; part, its required to replace single $ with double $$, so docker-compose does not treat it as an env variable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is there more?
&lt;/h2&gt;

&lt;p&gt;Of course there is. Traefik supports not only HTTP, but also TCP connections (like the one to a database). It also comes with different middlewares built in. You can easily add custom headers, rate limiting, redirects, retries, compression, circuit breaker, custom error pages, etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Creating a reverse proxy server with Traefik, including load balancing, web application security, service discovery or even SSL termination with automated Let's Encrypt certificates is quite easy.&lt;/p&gt;

&lt;p&gt;I was able to cover all my needs within minutes, and even more - I managed to create a simple review apps environment combined with our Gitlab CI instance. If you are interested in how I did that - give me a shout and subscribe to our newsletter to not miss that article.&lt;/p&gt;

&lt;p&gt;If you are interested in &lt;a href="https://accesto.com/blog/what-is-docker-and-why-to-use-it/"&gt;Docker&lt;/a&gt;, check out my e-book: &lt;a href="https://accesto.com/books/docker-deep-dive/"&gt;Docker deep dive&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.ops.io/images/-1DyMkSep_zK7gkQ0nmi1ZKYj9JXTw_2cT-DcPR8JbQ/w:880/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkub3BzLmlv/L3JlbW90ZWltYWdl/cy91cGxvYWRzL2Fy/dGljbGVzL2p0bmg1/eDY1ZXJ3NTRiY25x/dHFuLnBuZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/-1DyMkSep_zK7gkQ0nmi1ZKYj9JXTw_2cT-DcPR8JbQ/w:880/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkub3BzLmlv/L3JlbW90ZWltYWdl/cy91cGxvYWRzL2Fy/dGljbGVzL2p0bmg1/eDY1ZXJ3NTRiY25x/dHFuLnBuZw" alt="Image description" width="880" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Check out my previous articles on Docker networking in order to separate services and test failure scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://accesto.com/blog/docker-networks-explained-part-1/"&gt;Docker Networks - part 1 - basic concepts behind Docker networks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://accesto.com/blog/docker-networks-explained-part-2/"&gt;Docker Networks - part 2 - network separation and chaos monkey (simulating network issues)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>docker</category>
      <category>traefik</category>
      <category>tutorials</category>
    </item>
  </channel>
</rss>
