Auto-route multiple web projects using Traefik
Tags: traefik, docker, webdev
Published: 2024-03-08 17:26:08

_When I first joined a company, they had a method to deploy web projects onto dev and stage servers. However, I spotted critical flaws and inefficiency immediately._

## **Problem:** The scenario was...

1. To _manually_ build Docker image on _personal_ machine, then upload to _public_ Docker Hub! _(Yes, you read it right, cooperate secrets leaked.)_

1. To _manually_ edit DNS records each time when setting up new projects. It's not a problem for "production" deployment, but it is not the case here. Those are short-lived projects for reviews, and there are many legacy records cluttered up.

1. There are multiple _over-specced_ VMs just to serve static websites, and they had to choose which one to swap with new project each time.

1. Connections are not HTTPS. Although it is _technically_ not a problem, but annoying.

1. They have also complained significant times wasted to push and pull from Docker Hub, and to manually SSH to VMs to spin up projects. _(Though, I didn't experience it since this practice was killed by me immediately.)_

## **Solution:**

1. I set a wild-card in DNS record to point to a single VM.

1. That VM serves as Docker host.

1. I configured Traefix proxy (a container) to handle routing between projects (containers) and to handle HTTPS connections.

1. I wrote custom GitHub Actions workflow to handle deployment automatically.

1. _Repeat above steps for dev and stage VMs._

## The Main Topic:

_I'll talk about how I setup Docker host and Traefix container in this article, and I have separate article about [my custom workflow](https://dev.to/hwupu/my-github-actions-workflow-for-deploying-web-projects-37ni)._

### System Architecture

![system architecture of my solution (explain below)](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/89m5gfppebw6054b7ec8.png)

{% details _I don't know how to post the graph in hi-res, but **expand this for mermaid code**_ %}
```mermaid
flowchart
  subgraph Local Dev Env
  LD1 --> LD2 --> LD3
  LD1[Developing]
  LD2[Testing]
  LD3[Commit]
  end
  
  LD3 --> GH1
  
  subgraph GitHub
  GH1 --> GA1
  GH1[main branch]
    subgraph GitHub Actions
    GA1 --> GA2 --> GA3 --> GA4 --> GA5 --> GA6 --> GA7 --> GA8
    GA1[Activate gcloud service account]
    GA2[Config docker to use Artifacts Registry]
    GA3[Generate static webpages]
    GA4[Build custom nginx docker image]
    GA5[Push to Artifacts Registry]
    GA6[Stop existing docker container on remote host]
    GA7[Copy docker-compose to remote host]
    GA8[Docker-compose up on remote host]
    end
  end
  
  IA1 -. credential .-> GA1
  GA5 -.-> AR1
  
  subgraph Google Cloud IAM
  IA1[Actifacts Registry Service Account]
  IA2[Cloud DNS Service Account]
  end
    
  CD1 -.-> TR1
  
  subgraph Google Cloud DNS
  CD1 --> CD2
  CD1[some-domain-name.com]
  CD2[Manually add records for *.some-domain-name.com]
  end
  
  subgraph "Docker Host (on Google Cloud)"
  GA8 --> VM1
  VM1 --> VM2 --> VM3 --> AP1
  VM1[docker-compose up]
  VM2[pull new image from repository]
  VM3[docker run]
      
    subgraph Traefik
    IA2 -. credential .-> TR2
    TR1 -.-> TR2 -.-> TR3 -.-> TR4 --> TR5
    TR1[Setup reverse proxy]
    TR2["Signing TLS (Let's Encrypt)"]
    TR3[Listen on docker.sock]
    TR4[Dynamic config routing]
    TR5[Dynamic config middlewares]
    end
        
    subgraph App1
    AP1 --> TR4
    AP1[Nginx web server]
    end
    
  end
  
  AR1 -.-> VM2
  subgraph Google Cloud Actifacts Registry
  AR1[Repository]
  end
```
{% enddetails %}

As aforementioned, we will have:

1. An Ubuntu VM to run Docker engine.

1. Configure Traefik proxy (container) to handle routing between other containers, and to handle HTTPS connections.

1. Google Artifact Registry to store private Docker images.

1. Configure GitHub Actions workflow to glue all parts together.

1. Additionally, we will need a SSH key pair to connect to VM, and IAM credentials to access gcloud services.

### Configure Traefik Proxy

_Assuming we have Ubuntu VM with Docker and Docker Compose installed, and both Cloud Artifacts Registry and IAM setup properly. (Let me know in the comment if anyone wish for extra walkthrough)_

Within the `docker-compose.yml` for the Traefik container, we will add:

```yaml
version: "3.8"

services:
  traefik:
    # You may want to specify a version.
    image: "traefik:latest"

    # Name to your own liking.
    container_name: "traefik-proxy"

    command:
      - "--entrypoints.web.address=:80"
      - "--providers.docker"
      - "--providers.docker.exposedbydefault=false"
      - "--providers.docker.network=proxy"
      - "--api"

    ports:
      - "80:80"

    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"

    labels:
      - "traefik.enable=true"
      - "traefik.port=80"
      - "traefik.http.routers.traefik.rule=Host(`traefik.some-domain-name.com`)"
      - "traefik.http.routers.traefik.service=api@internal"
      - "traefik.http.routers.http-catchall.rule=hostregexp(`{host:.+}`)"
      - "traefik.http.routers.http-catchall.entrypoints=web"

    networks:
      - "proxy"

networks:
  proxy:
    driver: "bridge"
    name: "proxy"

```

Before you start the container, you should do the following once:

1. Create a bridge network:
    ```sh
    docker network create proxy
    ```

This configuration allows Traefik to setup new url whenever new docker container spins up, _over HTTP for now_.

For example, your VM is `some-domain-name.com` and a new container configured with hostname of `aaa.some-domain-name.com`. Traefik will configure the route of `aaa.some-domain-name.com` to the container automatically.

### Configure Traefik to redirect to HTTPS

We will configure Traefik to renew certificate with Let's Encrypt by using DNS challenge. Merge following lines into `docker-compose.yml`:

```yaml
services:
  traefik:
    # For simplicity, let's ignore aforementioned lines.

    command:
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.leresolver.acme.dnschallenge=true"
      - "--certificatesresolvers.leresolver.acme.dnschallenge.provider=gcloud"
      - "--certificatesresolvers.leresolver.acme.email=your-registered@email.com"
      - "--certificatesresolvers.leresolver.acme.storage=/acme.json"

    environment:
      - "GCE_PROJECT=your_GCP_project_id"
      - "GCE_SERVICE_ACCOUNT_FILE=lets-encrypt.json"

    ports:
      - "443:443"

    volumes:
      - "./acme.json:/acme.json"
      - "./lets-encrypt.json:/lets-encrypt.json"

    labels:
      - "traefik.http.routers.traefik.tls.certresolver=leresolver"
      - "traefik.http.routers.traefik.tls.domains[0].main=*.some-domain-name.com"
      - "traefik.http.routers.traefik.tls.domains[0].sans=some-domain-name.com"
      - "traefik.http.routers.traefik.entrypoints=websecure"
      - "traefik.http.routers.http-catchall.rule=hostregexp(`{host:.+}`)"
      - "traefik.http.routers.http-catchall.entrypoints=web"
      - "traefik.http.routers.http-catchall.middlewares=redirect-to-https"
      - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"

```

Please notes that the example shown is using Google Cloud DNS, you may have to set to a different challenger and different environment variables. Follow this [doc for gcloud challenger](https://go-acme.github.io/lego/dns/gcloud/), or follow [this index for specific instruction of your DNS providers](https://doc.traefik.io/traefik/https/acme/#providers).

Before you start the container, you should do the following once:

1. Create empty ACME file for Traefik to store Let's Encrypt info:
    ```sh
    touch acme.json
    chmod 600 acme.json
    ```

1. Download the Google Cloud Artifacts Registry service account credentials (json file), and rename it to `lets-encrypt.json`. _(or do the reverse in `docker-compose.yml`.)_

Kindly note that both `acme.json` and `lets-encrypt.json` should be within the same folder of `docker-compose.yml`.

🎉 Walla! You should have working Traefik proxy running and should have valid certificate to serve HTTPS. _(Actually it's not that easy, please refer to [official docs](https://doc.traefik.io/traefik/user-guides/docker-compose/acme-dns/) for troubleshooting; or, leave comments below, if I happened to pickup, no guaranty._

I hope this article is inspiring for you, and maybe, assist you to learn, or setup, your own Traefik proxy. I found it quite a deep learn curve initially, as there aren't much example configs that suites my scenario.

### Configure other projects to use this Traefik proxy:

As it is quite lengthly, please follow my second article for [custom GitHub Actions workflow and docker-compose.yml](https://dev.to/hwupu/my-github-actions-workflow-for-deploying-web-projects-37ni).

## **Feedbacks:** from colleague,

1. _"They said" it was a hour of "labour" work_ to deploy a version of project before, but I made it **done under 3 minutes**, and automatically.

1. I cut **cloud expenses by half**.