Tag Archive: docker

Create and Trust Local SSL Certificate

I use Jason Wilder’s nginx reverse proxy container as the gateway to my various Docker dev environments. Among it’s other services, it provides SSL termination, so I don’t need to worry about configuring SSL in every container I run.

The set up is pretty simple. Make a directory of certificates and mount it into the container at /etc/nginx/certs. In docker-compose.yml, it would look something like:

version: "2"
services:
  proxy:
    image: jwilder/nginx-proxy
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/certs:/etc/nginx/certs
      - /var/run/docker.sock:/tmp/docker.sock

You’ll need to create a new certificate for each domain you want to serve. Add them to the certs dir, and the proxy will find them and serve those domains with SSL.

I’ve created a script that will create the certificate and, on OS X at least, add it to your login keychain as a trusted certificate so you can avoid SSL warnings from your browser. Create the file create-cert.sh in your certs directory and run it from there. E.g., certs/create-cert.sh mysite.dev.

#!/bin/bash
 
 
CERTDIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
DOMAIN=$1
 
if [ $# -lt 1 ]; then
  echo 1>&2 "Usage: $0 domain.name"
  exit 2
fi
 
cd ${CERTDIR}
 
cat > ${DOMAIN}.cnf < <-EOF
  [req]
  distinguished_name = req_distinguished_name
  x509_extensions = v3_req
  prompt = no
  [req_distinguished_name]
  CN = *.${DOMAIN}
  [v3_req]
  keyUsage = keyEncipherment, dataEncipherment
  extendedKeyUsage = serverAuth
  subjectAltName = @alt_names
  [alt_names]
  DNS.1 = *.${DOMAIN}
  DNS.2 = ${DOMAIN}
EOF
 
openssl req \
  -new \
  -newkey rsa:2048 \
  -sha1 \
  -days 3650 \
  -nodes \
  -x509 \
  -keyout ${DOMAIN}.key \
  -out ${DOMAIN}.crt \
  -config ${DOMAIN}.cnf
 
rm ${DOMAIN}.cnf
 
if [[ $OSTYPE == darwin* ]]; then
  sudo security add-trusted-cert -d -r trustRoot -k $HOME/Library/Keychains/login.keychain ${DOMAIN}.crt
fi

Reload your proxy, and you can now visit https://mysite.dev/.

Reaching localhost from a Docker container

Docker Engine has some built-in networking features to allow containers to communicate with each other and with the outside world. For example you can link two containers together to allow them to talk to each other, either via the docker run command or in your docker-compose.yml.

version: "2"
services:
memcached:
image: memcached:1.4-alpine
php:
image: php:5.6-fpm
links:
- memcached
nginx:
image: nginx:stable-alpine
links:
- php
ports:
- "80"

In our example, the php container can communicate with the memcached container, the nginx container can communicate with the php container, and the nginx container exposes its port 80 to receive connections from, for example, your web browser.

In some cases, though, your container needs to be able to reach back out to your host system. The specific use case I have is debugging with Xdebug. Using Docker for Mac, there’s not a reliable address you can use to make that connection. A container that tries to connect to localhost or 127.0.0.1 will be talking to itself, not to your host machine.

To work around this, I set up an additional IP address for my host OS’s loopback interface. I use 10.254.254.254, but you may find it conflicts with other applications you have running, so any other local address will be effective.

The command to set up this address is:

sudo ifconfig lo0 alias 10.254.254.254

Running this will allow any container you have running to connect back to your host OS using the address 10.254.254.254. You can, for example, set this in your php.ini as the remote host address for Xdebug (I prefer to set it in a reverse proxy configuration, but that’s a topic for a later post).

xdebug.remote_host=10.254.254.254

Verify that it worked with:

ifconfig lo0

Your new address should appear there along with a few other defaults.

You’ll have to run the command after every boot, unless you set a launchd script to do it for you. There are a few versions of the launchd plist file floating around, or you can make your own from one of them. Copy it into /Library/LaunchDaemons/ and your new loopback address will be set for you on every boot.

Data Persistence with Docker Volumes

Docker containers are designed to be ephemeral. You can destroy one and spin up an exact replica in seconds. Everything that defines the container can be found in the Dockerfile that declares how to build it.

This model does not, however, explain what to do with persistent data. Things like databases or uploaded media. In a production environment, I would recommend delegating these tasks to an external service, like Amazon’s RDS or S3.

For local development, though, you can use volumes for storing persistent data. Volumes come in two main flavors: data volumes and host directory mounts.

The latter is perhaps the most straightforward. You connect a directory in your container to a directory on your host machine, so they are essentially sharing the file system. Indeed, when you’re actively working on code, this is the simplest way to share your local code with your running containers. Mount the root directory of your project as a volume in your container, and anytime you update code, your container will also have the updates.

# docker run --rm -it -v="/your/local/dir:/srv/www/public" nginx:stable-alpine /bin/sh

This runs a container that has its /srv/www/public directory shared with your host system’s /your/local/dir directory. Updates you make to files either on your local system or in the container are automatically shared with the other.

Data volumes do not map directly to your host filesystem. Docker stores the data somewhere, and you generally don’t need to know where that is. When using Docker for Mac, one of the key differences is that a host mount shares files using osxfs (which currently has some performance issues) while a data volume stores its data inside the Docker virtual machine (which is subsequently much more performant for I/O). While I use host mounts for things like uploaded media, I prefer to use a data volume for storing databases.

# docker volume create --name=mysqldata

Once the volume is created, you can mount it into one or more containers.

# docker run --rm -v="mysqldata:/var/lib/mysql" mysql:5.5

The contents of our volume “mysqldata” will be available to MySQL in the /var/lib/mysql directory. The data volume itself doesn’t have a directory name (in contrast to the prior best practice of using a directory within a data-only containers). I think of a volume as a single directory that can be mounted wherever I want in a container.

Dev with Docker

I’ve been using Docker to run my local development environments for nearly two years now. In that time, it has gone from experimental and arcane, to almost stable and user-friendly.

Docker logoI work on a MacBook Pro, in OS X. After a brief dive into the details of what Docker is and how it works, you learn that you can’t run the Docker engine in OS X. Containers require process isolation and resource management features of the Linux kernel, so you can only run the Docker engine in Linux.

There are a number of workarounds for this, all of which involve using a virtual machine. Initially, I used boot2docker, which loaded a small VM using VirtualBox. Eventually, this was subsumed into Docker Machine and the Docker Toolbox, which used the same VM in VirtualBox but will slightly improved tools. Now I’m using Docker for Mac, which still uses a boot2docker-based virtual machine, but it runs on the hypervisor framework built into OS X, obviating the requirement for a separate hypervisor like VirtualBox.

With the notable exception of filesystem read performance, Docker for Mac has removed many of the more tedious aspects of running a local Docker environment, and had made development much easier.

This is the first in an anticipated series of posts documenting how I have my local development environment configured, focusing on using Docker for Mac for WordPress development.