Two days. That is how long the new colleague spent last month getting one project to run on his laptop. The README is two pages: PHP with a specific set of extensions, Nginx, MySQL, Redis, and a paragraph that starts with “on OS X it is a bit different”. Every laptop in the team is a slightly different snowflake.

So I finally tried Docker for local development. The compose file:

version: '2'
services:
  php:
    build: ./docker/php
    volumes:
      - .:/var/www/app
  nginx:
    image: nginx:1.9
    ports:
      - "8080:80"
    volumes:
      - ./docker/nginx.conf:/etc/nginx/conf.d/default.conf
  mysql:
    image: mysql:5.6
    environment:
      MYSQL_DATABASE: app
    volumes:
      - dbdata:/var/lib/mysql
volumes:
  dbdata:

The php image is a small Dockerfile: official php:5.6-fpm plus docker-php-ext-install for the extensions from that README paragraph. The paragraph is code now. It cannot drift.

Things I learned the hard way. State does not live in containers. Database files go to a named volume, otherwise docker-compose down eats your test data and you learn about volumes emotionally. Configuration goes through environment variables, and a file edited inside a running container is gone after the next restart. A container is a process. Not a small server you SSH into and garden.

The ugly part is file permissions on Linux. The code is bind-mounted, PHP-FPM writes cache files as its own UID, and suddenly your host user cannot delete them. We pass the host UID into the build and create a matching user. Not elegant. Works.

One warning. The dev image mounts the code from the host. A production image should copy the code in, so the artifact is complete and immutable. Do not ship the dev compose file to a server and call it deployment.

Setup for the next person: twenty minutes, most of it downloading images. The README now says docker-compose up. I am about two years late to this.