Adding a new Ghost via docker-compose to your traefik setup
Tags: traefik, docker, ghost, devops
Published: 2024-02-02 19:31:30

Sometimes the easiest and quickest way to try (or even deploy) a new service is by using the recommended docker-compose-setup that they often have as an example. But if you have an existing infrastructure, like we do with the great [mother of all self-hosting](https://github.com/mother-of-all-self-hosting/mash-playbook) ansible playbooks, this isn't always easy to integrate. In particular when that infrastructure is managed and started and stopped independently from the additional docker-compose you intend to add. Lucky, who is running their out-most proxy using traefik, because with just a few extra labels your docker-compose becomes available TLS-certs included.

Fortunately for us the MASH-playbook uses traefik and so adding a Ghost setup for testing was quick and easy. Let's look at the docker-compose (for our fictional `blog.example.org`-address) and then we'll explain some of the specific aspects to address:
```yaml
version: '3.1'

services:

  ghost:
    image: ghost:5-alpine
    restart: unless-stopped
    environment:
      # see https://ghost.org/docs/config/#configuration-options
      database__client: mysql
      database__connection__host: db
      database__connection__user: root
      database__connection__password: SOME_PRIVATE_ROOT_PASSWORD
      database__connection__database: ghost
      # this url value is just an example, and is likely wrong for your environment!
      url: https://blog.example.org
    volumes:
      - ./data/ghost:/var/lib/ghost/content
    networks:
      default:
      traefik:
        aliases:
         - blog-example-org

    labels:
      - traefik.enable=true
      - traefik.docker.network=traefik
      - traefik.http.routers.blog-example-org.rule=Host(`blog.example.org`)
      - traefik.http.services.blog-example-org.loadbalancer.server.port=2368
      - traefik.http.routers.blog-example-org.entrypoints=web-secure
      - traefik.http.routers.blog-example-org.tls=true
      - traefik.http.routers.blog-example-org.tls.certResolver=default
      - traefik.http.routers.blog-example-org.service=blog-example-org
  db:
    image: mysql:8.0
    restart: unless-stopped
    networks:
     - default
    environment:
      MYSQL_ROOT_PASSWORD: SOME_PRIVATE_ROOT_PASSWORD
      MYSQL_DATABASE: ghost
    volumes:
      - ./data/db:/var/lib/mysql


networks:
  traefik:
    external: true
  default:
    external: false
```

Alright, there's a few things here that have changes compared to the default example from ghost. We will be ignoring the specific Ghost and MySQL changes as they aren't that relevant but are only included for completeness.

## The networks
First and foremost, we have the additional `networks`-section a the bottom of the configuration with two networks: `default` which we will use for this specific service and the other that is bridging to the `traefik`-service, which is marked as `external: true` telling docker to use the existing set up network. This must the the network the dockerized `traefik` is using. In the case of MASH this is just called `traefik` as well.

Secondly we need to the `networks`-section to both our services, where any internal service is only on the `default` network and the exposed service must also be on the `traefik`-network. Here we also give it some specific DNS name within that network for traefik to route the traffic to.

## the `traefik` labels
When `traefik` is set up to use docker-labels, which is the case in our MASH setup, we can just label our service with a view fields and the `traefik` service will automatically recognize and configure the routing appropriately. Let's go through them one by one:

- `traefik.enable=true`: to configure traefik to route this one. Depending on your setup this might not be needed
- `traefik.docker.network=traefik`: the network traefik is on
- `traefik.http.routers.blog-example-org.rule=Host(\`blog.example.org\`)`: the actual hostname we want this service to be available under between the final ticks. Note that we are creating a custom traefik-`router` for this called `blog-example-org`, all the following configuration is also using that router prefix:
- `traefik.http.services.blog-example-org.loadbalancer.server.port=2368`: the port on this service the traffic should be routed to
- `traefik.http.routers.blog-example-org.entrypoints=web-secure`: if the traefik has multiple outsid eendpoints, which ones to serve - in the MASH case we want this to be available at `https`, which is named `web-secure` in our setup.
- `traefik.http.routers.blog-example-org.tls=true`: to enable TLS for this router
- `traefik.http.routers.blog-example-org.tls.certResolver=default`: use the default DNS cert resolving functionality. In MASH this means we are using lets-encrypt certification
- `traefik.http.routers.blog-example-org.service=blog-example-org`: the service to route the traffic to. The value is the dns-alias we gave in the network configuration before.


## up and go

And that's about it. Assuming the DNS name already resolves to your server and your traefik is already running just doing a `docker compose up -d` and a short time later (if it needs to fetch the certificates for the first time), the service will be routed through and be available at `blog.example.org`. Neat!