Serves you right!

Posted on 2018-01-03

A new post for a new year! I spent my previous post advocating Spacemacs after having been a heavy Vim user for many years. I’ll definitely get back to my editor setup in future posts, but before that I thought I’d do a quick post about the server setup I’m currently using for this site. It’s not overly complex, so the post won’t be too long (hopefully).

Linode

I’ve been using Linode as a VPS provider for quite some time. I won’t say I’m a power user when it comes to hosting. Linode gives me more than enough freedom to run the services I want, at a comfortably low price-point ($5/month).

The Linode configuration interface makes it easy to setup and manage server instances, configure DNS and networking, emergency login shells, automated backups, and whatnot. Like I said though, I’m not a power user so I can’t really comment on all of the features they provide.

Docker

I have been running servers for many years, although mostly for personal (non-)use1. This time, instead of installing everything using standard package managers and global installations, I decided to give Docker a go.

Due to the simplicity of my current setup, I’m using Docker Compose2 to orchestrate the few services I use. It’s using a yaml configuration file to define and manage the different images used for each service. My configuration isn’t overly complicated, once you look past the fact that most of it is simply specifying mount points for the different services. The images I currently use are vanilla images fetched from the Docker Hub.

nginx

The nginx image provides a vanilla Nginx server packaged for Docker. There are two currently in my setup:

  1. The internet-facing reverse proxy.
  2. The static server serving the contents of this blog.

It’s fairly obvious that running muliple instances of Nginx may be quite overkill, but to me the convenience this setup provides trumps it. Hopefully it becomes clear why when talking about the other two services.

version: "3.2"

services:
  nginx:
    restart: always
    image: nginx
    container_name: nginx
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - "/data/nginx/certs:/etc/nginx/certs:ro"
      - "/data/nginx/conf.d:/etc/nginx/conf.d:ro" 
      - "/data/nginx/html:/usr/share/nginx/html:ro"
      - "/data/nginx/vhost.d:/etc/nginx/vhost.d"

  myme.no:
    restart: always
    image: nginx
    container_name: myme.no
    depends_on:
      - letsencrypt
    volumes:
      - "/data/myme.no/nginx:/etc/nginx/conf.d:ro"
      - "/data/myme.no/public:/usr/share/nginx/html:ro"
    environment:
      - VIRTUAL_HOST=myme.no
      - LETSENCRYPT_HOST=myme.no
      - LETSENCRYPT_EMAIL=myrseth@gmail.com

jwilder/docker-gen

docker-gen is a smart little image which uses the Docker APIs to determine when containers come and go and dynamically adds a virtual host configuration for them in the reverse proxy. This means that it’s as simple as firing up a new container with the correct VIRTUAL_HOST configuration, and suddenly a new virtual host is readily configured in the proxy.

Now adding and removing vhosts is not something I do often, to be honest. But the simplicity of it all makes adding new services effortless.

nginx-gen:
  restart: always
  image: jwilder/docker-gen
  container_name: nginx-gen
  depends_on:
    - nginx
  volumes:
    - "/data/nginx/certs:/etc/nginx/certs:ro"
    - "/data/nginx/conf.d:/etc/nginx/conf.d:rw"
    - "/data/nginx/templates/nginx.tmpl:/etc/docker-gen/templates/nginx.tmpl:ro"
    - "/data/nginx/vhost.d:/etc/nginx/vhost.d:ro"
    - "/var/run/docker.sock:/tmp/docker.sock:ro"
  entrypoint: >-
    /usr/local/bin/docker-gen
      -notify-sighup nginx
      -watch
      -wait 5s:30s
      /etc/docker-gen/templates/nginx.tmpl 
      /etc/nginx/conf.d/default.conf

jrcs/letsencrypt-nginx-proxy-companion

The final piece of the puzzle is an image which creates and maintains Let’s Encrypt certificates for all exposed services. This means you get HTTPS for free, without lifting a finger. The image also ensures that certificates are renewed automatically when they close in on their expiration date.

Now there’s no reason not to serve secure pages using HTTPS. Here’s that part of the config:

letsencrypt:
  restart: always
  image: jrcs/letsencrypt-nginx-proxy-companion
  container_name: letsencrypt
  depends_on:
    - nginx-gen
  volumes:
    - "/data/nginx/certs:/etc/nginx/certs:rw"
    - "/data/nginx/html:/usr/share/nginx/html:rw"
    - "/data/nginx/vhost.d:/etc/nginx/vhost.d:rw"
    - "/var/run/docker.sock:/var/run/docker.sock:ro"
  environment:
    - NGINX_DOCKER_GEN_CONTAINER=nginx-gen
    - NGINX_PROXY_CONTAINER=nginx

That’s it folks!

As I’m getting older I realize that I want more and more of what I use in my daily life to just work. I’m definitely not as eager as I was before to tinker around with things just for fun.3 So even though I ended up running multiple instances of Nginx and managing my services using Docker, I feel like my current setup is simple in the sense that I don’t have to do much to have it work as I intend it to.

Footnotes


  1. The servers I’ve been running have had a tendency to get neglected and not really used for anything purposeful, or just running IRC clients in Tmux.↩︎

  2. Supposedly Docker Compose is not advised to be used in production. Besides the fact that my setup hardly qualifies as “production”, it seems more than stable enough for my needs.↩︎

  3. That’s not completely true though, as I do have a tendency to start short-lived (or active) coding projects just to try out some stuff.↩︎