One server to host them all, subdomains to find them, docker-compose to bring them all, and a reverse proxy to bind them

Aug 25, 2017 20:00 · 903 words · 5 minutes read

Yes that title is a Lord of the Rings reference.

I’ve settled on a solution for hosting my personal projects that strikes(for me) a happy balance of cheap, configurable, and easy.

The setup is simple:

  • A single VM running docker
  • A domain with DNS A records pointing it and sub-domains at the VM’s IP
  • A container running Traefik monitoring docker
  • For each new project: apply the appropriate docker container labels so Traefik knows how to route its traffic based on sub-domain.

I’ve dubbed my setup Yggdrasil after the tree that joins the nine worlds in Norse mythology.

yggdrasil

Pro’s

The main advantage to this implementation is that it’s only one server. I pay as little as possible for hosting, but while still maintaining a large degree of control (ssh access, etc.).

  • Cheap
  • Flexible
  • Docker isolation
  • Automatic SSL certs
  • Fast/minimal spin-up for a new project

Con’s

The main downside to the current implementation is that it’s only one serevr. Once the number of people accessing my demo’s scales up, the server will be overloaded. For now I’m pretty unpopular, so yay…

Luckily this approach can also be applied to a Kubernetes cluster. I’ll cross that bridge when I get to it.

  • Doesn’t scale
  • Could be more automatic
  • Docker knowledge required to manage

The Setup

Hosting

For my hosting I use a single VM (droplet) hosted by Digital Ocean. At $5/month it’s one of the cheapest offerings you can find. There may be something cheaper but I’m happy for now.

I created my VM using docker-machine (Digital Ocean instructions). This auto-spins up a VM running the docker-daemon and makes it simple to dive in interacting with the daemon remotely using docker commands.

DNS

All of my projects are hosted as sub-domains of erdmanczyk.com (I personally purchased my domain from gandi.net).

I wanted all requests for erdmanczyk.com and subdomains to route to my single Digital Ocean droplet, so I configured @ and *(wildcard) A records for its IP address.

  • @ routes requests for erdmanczyk.com
  • * requests for any subdomains e.g. foo.erdmanczyk.com, bar.erdmanczyk.com.

How to do that with your DNS provider will vary, but for those using ghandi.net here’s A-record instructions.

Traefik Reverse Proxy

At the heart of it all is the Traefik reverse proxy and docker.

Traefik is an open source reverse proxy written in Go chosen for a couple features that empower this setup:

  • Automatically obtaining/renewing (free) SSL certs for backends via LetsEncrypt
  • Route requests to exposed containers based on container labels

The first means free SSL certificates and nearly zero effort to manage them. The latter means if I want a project on a new sub-domain I simply apply the correct host label to the container I want to expose, then traefik will automatically handle routing requests for the new <foo>.erdmanczyk.com subdomain to that container.

Less ops, more time writing code.

Traefik itself is also runs in a docker container, with a minimal configuration file.

Luckily I didn’t need how to configure Traefik for this setup myself, the work below is piggybacked from the repo containerize-my-server/reverse-proxy. Forked into my implementation.

Here’s a snippet of the docker-compose file.

version: '3.2'

services:
  traefik:
    build: .
    command: --logLevel=DEBUG
    ports:
      - "80:80"
      - "443:443"
      - "8080:8080"
    restart: always
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    networks:
      - reverseproxy
    cap_drop:
      - all
    cap_add:
      - net_bind_service

networks:
  reverseproxy:
    driver: bridge

Of note here is a few things:

  • The docker socket is mounted so Traefik can monitor containers spinning up/down
  • A network is used for allowing other docker-compose configurations to connect to the same network as Traefik (so Traefik can route)
    • docker-compose by default isolates separate projects into different networks, this curcumvents that

And the traefik configuration is in config.toml

# defaultEntryPoints must be at the top because it should not be in any table below
defaultEntryPoints = ["http", "https"]

[web]
# Port for the status page
address = ":8080"

# Entrypoints, http and https
[entryPoints]

# http should be redirected to https
[entryPoints.http]
address = ":80"
[entryPoints.http.redirect]
entryPoint = "https"

# https is the default
[entryPoints.https]
address = ":443"

[entryPoints.https.tls]

# Enable ACME (Let's Encrypt): automatic SSL
[acme]
# caServer = "https://acme-staging.api.letsencrypt.org/directory"
email = "<your email>"
storage = "acme.json"
entryPoint = "https"
onDemand = false
OnHostRule = true


[docker]
endpoint = "unix:///var/run/docker.sock"
domain = "<your domain>"
watch = true
exposedbydefault = false

A few things of note in config/toml:

  • all http is redirected to https
  • The [acme] section instructs Traefik to automatically obtain/renew certs from LetsEncrypt
  • The [docker] section instructs Traefik to monitor docker

Docker-Compose my project

The last step is to actually spin up my projects.

For any new projects I just need to apply a few labels:

  • traefik.enable=true
  • traefik.backend=<project name>
  • traefik.frontend.rule=Host:<fully qualified domain domain>
  • traefik.docker.network=reverseproxy_reverseproxy

These indicate the Traefik the network, subdomain, etc. to use for routing traffic to this instance. It’s also important that port 80 is exposed by the container (but not externally to the host). The server within the container should listen on port 80 with no TLS.

As an example, I’ve created a template Go project with a minimal config that’s deployable via the Yggdrasil setup. Named twig, because of the whole Norse tree thing. The Go server simply listens on port 80 and repeats back the route requested. Check it out at https://twig.erdmanczyk.com

In summary

If you want something cheap but with some degree of control maybe this setup is for you. This post is here to share to hopefully save those with the same whims some searching.