This is going to be a fairly rough guide to how I upgraded and containerized the various NJPineBarrens.com sites that used to run on a single LAMP stack on my Linode VPS.

I had a fairly straightforward setup. I run Xenforo forum software, WordPress for articles and blog posts, and some basic static html sites (images.njpinebarrens.com, common.njpinebarrens.com which held some php common to my WordPress and Xenforo themes, and maps.njpinebarrens.com which holds a Google Maps/WMS mashup I cobbled together in Javascript.) Having worked at Princeton University I gained a healthy respect for security and realized that I wanted to isolate Xenforo and WordPress from each other in addition to putting everything behind a reverse proxy and enable https everywhere. My CentOS 6 VPS was also getting long in the tooth so I wanted to replace it with CentOS 7.

I figured that it was worth spending the time to gain some Docker skills, and once I found the Jwilder nginx reverse proxy/Let’s Encrypt companion I knew I was on the right track. I decided to stand everything up using Docker-Compose since it seems more efficient to put everything in compose scripts than launch the containers by hand.

I spun up a new VPS at Linode, ran updates, and installed Docker. It’s then simply a matter of adding the Docker CE repo to the server and doing a yum install along with some prerequisite packages.

sudo yum install yum-utils device-mapper-persistent-data lvm2
sudo yum-config-manager --add-repo \ 
  https://download.docker.com/linux/centos/docker-ce.repo
sudo yum install docker-ce
sudo systemctl start docker

and to set Docker to start on boot:

sudo systemctl enable docker

Now it’s time to install docker-compose, which sadly doesn’t come as part of the basic Docker CE install. This part is super easy:

curl -L https://github.com/docker/compose/releases/download/1.22.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose

Note that docker-compose 1.22.0 was the latest version at the time of writing. You can check here to see if there’s a newer version.

Then I began the actual process of setting up the containers. I put all of the compose scripts into a directory structure that looks something like this:

/docker
  /njpinebarrens.com
    /www
    /forums
    ...
  /nginx-proxy

I don’t know if that’s the “proper” way to do it but it makes logical sense to me. Plus I figured that it might be easier to put it in version control as a single project.

I first started with creating the network that all of the containers and the proxy would use. Just a simple matter of:

docker network create nginx-proxy

Then I created the docker-compose file for the proxy and helper containers. There are three of them here: the nginx proxy itself, the nginx-gen container that handles creating the proxy configs for the other containers, and another container to handle fetching and renewing Let’s Encrypt certs. One of the great things about this setup is that the nginx-gen container will detect when a container that needs to be proxied starts up and will generate a config file on the fly for the nginx proxy container to handle. Once you have it all up and running it’s pretty much set it and forget it, although it took some trial and error to get working for me. Hopefully this helps other people trying to do the same thing. Copy this to a file named docker-compose.yaml:

version: '3'
services:
  nginx:
    image: nginx
    labels:
      com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy: "true"
    container_name: nginx-proxy
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./conf.d:/etc/nginx/conf.d
      - ./vhost.d:/etc/nginx/vhost.d
      - ./html:/usr/share/nginx/html
      - ./certs:/etc/nginx/certs:ro
    environment:
      DEFAULT_HOST: (whatever your default website is)

  nginx-gen:
    image: jwilder/docker-gen
    command: -notify-sighup nginx-proxy -watch -wait 5s:30s /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf
    container_name: nginx-gen
    restart: always
    volumes:
      - ./conf.d:/etc/nginx/conf.d
      - ./vhost.d:/etc/nginx/vhost.d
      - ./html:/usr/share/nginx/html
      - ./certs:/etc/nginx/certs:ro
      - /var/run/docker.sock:/tmp/docker.sock:ro
      - ./nginx.tmpl:/etc/docker-gen/templates/nginx.tmpl:ro

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

Some things to note – the environment variable DEFAULT_HOST for the nginx proxy basically says what the default website to send someone to if it receives a http request that doesn’t specify the hostname in the HTTP header. You’re also going to need the file nginx.tmpl (download it here) in the same directory as the docker-compose.yaml file. Also, there seems to be an issue with firewalld not playing nicely with Docker. When you bring containers up docker-compose is supposed to set up the necessary firewall rules. This works, but if you make any changes to firewalld outside of docker (say creating a rule to allow traffic to the Zabbix agent running on the server) it will break things. I ended up disabling firewalld and using iptables which seems to work better with Docker on CentOS 7.

Finally, to start the container you’re going to want to cd into the directory where that docker-compose.yaml file is and execute

docker-compose up

That will launch the container, which includes fetching the nginx and other containers from the Docker Hub. You’ll see a bunch of logs go by. Just let it do its thing until it seems to settle down and then Control-C to exit. Next time when you want to start the container just run a

docker-compose start

and the containers will start in the background. If you want to view the logs from the containers, make sure you’re in the directory with the compose file and type

docker-compose logs --tail=10 -tf

If you run a docker-compose logs it will show you all of the logs from the time the container was started. You probably don’t want to go that far back so the –tail=N flag will just tail show the last N lines. The -tf flag tells Docker to show timestamps with the logs as well as display any new logs as they happen.

In the next article we’ll set up our first container – Xenforo. The instructions in there should work for anybody looking to set up a LAMP type website.