Ride the Whale!

Docker for Drupalists

socketwench.github.io/rideTheWhale

"What's wrong with MAMP?"

Problem #1

No sandboxing

What happens outside the repo...

...stays on your system forever

Like pulling weeds

Files, databases, config, binaries

Takes time, disipline to find and remove

Docker sandboxes everything

Standard commands for delete

No matter the project or tech stack

Problem #2

Dev environment isn't repeatable

Instructions aren't enough

Humans are fallible, forgettable, fried

No Special Snowflakes

Docker containers repeatable by design

Built from a few simple text files

Problem #3

Dev environment isn't sharable

You can't share
a house of cards

That's what bespoke environments are

Adds hidden costs

On-boarding, troubleshooting, and recovery time

The DRY principle...

...should apply to servers too

Docker is sharable

Download from the internet

Add build files to your repo

Getting Docker

Do I need to pay for it?

Docker is free and open source!

github.com/docker/docker

Docker for Mac and Win

docker.com/products/docker

Docker 1.12+

Older Systems?

docker.com/products/docker-toolbox

Includes Virtualbox

Docker's Hypervisor

Embedded in Docker 1.12+

Virtualbox for earlier versions

Docker on Linux

No VM needed, uses host's kernel

Use your distro's package manager to install

Getting containers

Docker is like Vagrant

You rarely build containers yourself.

docker hub icon

Online repository of ready-to-use containers

hub.docker.com

Official Containers

Typically created by project maintainers

Apache, PHP, MySQL, and many Linux Distros

Official Drupal Container

hub.docker.com/_/drupal

Great for demos...not great for development.

Downloading Containers

docker pull image_name

image_name is the name on Docker Hub

Pulling Debian

$ docker pull debian

Good "base image" for web projects

Container tags

image_name:tag

Used for different versions or varients

Tags are optional

Default to latest

Unique per container, check page on Hub

Using containers

Running a container

docker run image_name
$ docker run -i -t debian /bin/bash
root@ffc6d3de3d27:/#
root@ffc6d3de3d27:/# uname -a
Linux ffc6d3de3d27 4.4.15-moby #1 SMP Thu Jul 28 21:30:50 UTC 2016 x86_64 GNU/Linux

Use exit to quit

Run in the background

$ docker run -d debian /bin/bash -c "while true; do sleep 1; done"
b4d333ad4d0f9c68249a091fdde53d25a09cbc785c5aba2a697b3a3ea04da778

$

Think "detach"

Container IDs

Unique identifier for that container instance

Use the first few characters as a shorthand

Listing containers

docker ps

Think "process"

Using a detached container

docker exec container_id /command/to/run

Using exec

$ docker exec -i -t b4d /bin/bash
root@b4d333ad4d0f:/#

Use exit to quit

Stopping containers by ID

docker kill container_id

Building a web stack

"A container is just a process"

Not exactly true, but close

LAMP needs multiple processes

One process = one container

docker compose icon

Manage "container sets" with one file

docker-compose.yml icon

Descriptive, not imperitive

Save in the root directory of your project!

A basic LAMP set

version: '2'
services:
  web:
    image: php:apache
  db:
    image: mariadb

Starting container sets

docker-compose up -d

docker-compose.yml must be in current directory!

Listing container sets

$ docker-compose ps
       Name                    Command             State    Ports
------------------------------------------------------------------
ridethewhale_db_1    docker-entrypoint.sh mysqld   Exit 1
ridethewhale_web_1   apache2-foreground            Up       80/tcp

Compose and container names

parentDirName_serviceName_index

Use unique project names!

/home/tess/projects/rideTheWhale
├── docker-compose.yml
└── docroot

Port mapping

Needed to access the container from the host

"portOnHost:portInContainer"

version: '2'
services:
  web:
    image: php:apache
    ports:
      - "80:80"
  db:
    image: mariadb

Updating containers

$ docker-compose kill
$ docker-compose up -d

Getting files in the container

Volumes

Creates a persistent directory inside a container

Mounts a directory or file in the container

Volumes in Compose

In the volumes section...

path/on/host:path/in/container

Our project so far

/home/tess/projects/rideTheWhale
├── docker-compose.yml
└── docroot
    └── index.html
version: '2'
services:
  web:
    image: php:apache
    ports:
      - "80:80"
    volumes:
      - ./docroot:/var/www/html
  db:
    image: mariadb

Specifying paths

Use relative paths to Compose file

Use absolute paths in the container

Kill, up, refresh...

Configuration

How to pass config?

Bake it into the container

Mount configuration files

Use environment variables

Setting variables

In the environment section...

VAR_NAME=VALUE

What vars to use?

Check Hub page for variable names and use

version: '2'
services:
  web:
    image: php:apache
    ports:
      - "80:80"
    volumes:
      - ./docroot:/var/www/html
  db:
    image: mariadb
    environment:
      - MYSQL_ROOT_PASSWORD=root
      - MYSQL_DATABASE=your_database_name
      - MYSQL_USER=your_database_user
      - MYSQL_PASSWORD=your_database_password
(Scroll down.)

Wait, no port?

Private network automatically created per set

Containers preconfigured with the right port open

version: '2'
services:
  web:
    image: php:apache
    ports:
      - "80:80"
    volumes:
      - ./docroot:/var/www/html
  db:
    image: mariadb
    ports:
      - "3306:3306"
    environment:
      - MYSQL_ROOT_PASSWORD=root
      - MYSQL_DATABASE=your_database_name
      - MYSQL_USER=your_database_user
      - MYSQL_PASSWORD=your_database_password
(Scroll down.)
$ docker-compose kill && docker-compose up -d
$ mysql -u your_database_user -p -h 127.0.0.1 -P 3306
Enter password:

Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 2
Server version: 10.1.16-MariaDB-1~jessie mariadb.org binary distribution

Copyright (c) 2000, 2016, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]>

Now for Drupal!

Prepping the files

Download to volume-mounted directory

Create, set permissions for files directory

Create services.yml and settings.php as normal

Customizing containers

dockerfile icon

Container "source code"

Creating Dockerfiles

Always named Dockerfile

Imperitive, not descriptive

Directories for containers

/home/tess/projects/rideTheWhale
├── .docker
│   ├── web
│   │   └── Dockerfile
│   └── db
├── docker-compose.yml
└── docroot
    └── index.html

Common, but only a suggestion

The first two lines

FROM php:apache
MAINTAINER tess@deninet.com

Turtles all the way down

All containers are based on another container

scratch container

Starting point for all containers

Just an empty *.tar.gz file

"Base image"

Base Linux install (Debian, CentOS, Arch, etc.)

Whatever image you start with

No INSTALL directive

Docker lets the container base image decide!

Install commands

Debian uses apt-get

CentOS uses yum

Some containers provide special install scripts!

RUN directive

RUN /command/to/run
FROM php:apache
MAINTAINER tess@deninet.com

RUN apt-get update && apt-get install -yq \
    libfreetype6-dev \
    libjpeg62-turbo-dev \
    libmcrypt-dev \
    libpng12-dev \
    libicu-dev

RUN docker-php-ext-install gd json intl pdo pdo_mysql mbstring

Dockerfiles and Compose

build: path/to/Dockerfile

Use instead of image

version: '2'
services:
  web:
    build: .docker/web
    ports:
      - "80:80"
    volumes:
      - ./docroot:/var/www/html
  db:
    image: mariadb
    ports:
      - "3306:3306"
    environment:
      - MYSQL_ROOT_PASSWORD=root
      - MYSQL_DATABASE=your_database_name
      - MYSQL_USER=your_database_user
      - MYSQL_PASSWORD=your_database_password
(Scroll down.)

No other lines changed?

We're just adding more to php:apache

Building a container set

docker-compose build optionalServiceName

Must be run before up!

Adding Drush

There's a container for that

hub.docker.com/r/drush/drush

Reusing volumes

Copy and paste volume sections

Used named volumes (Compose 2+)

Use volumes_from

version: '2'
services:
  web:
    build: .docker/web
    ports:
      - "80:80"
    volumes:
      - ./docroot:/var/www/html
  drush:
    image: drush/drush
    volumes_from:
      - web
  db:
    image: mariadb
    ports:
      - "3306:3306"
    environment:
      - MYSQL_ROOT_PASSWORD=root
      - MYSQL_DATABASE=your_database_name
      - MYSQL_USER=your_database_user
      - MYSQL_PASSWORD=your_database_password
(Scroll down.)
$ docker-compose ps
        Name                     Command             State            Ports
------------------------------------------------------------------------------------
ridethewhale_db_1      docker-entrypoint.sh mysqld   Up       0.0.0.0:3306->3306/tcp
ridethewhale_drush_1   drush                         Exit 0
ridethewhale_web_1     apache2-foreground            Up       0.0.0.0:80->80/tcp
$ docker-compose ps
        Name                     Command             State            Ports
------------------------------------------------------------------------------------
ridethewhale_db_1      docker-entrypoint.sh mysqld   Up       0.0.0.0:3306->3306/tcp
ridethewhale_drush_1   drush                         Exit 0
ridethewhale_web_1     apache2-foreground            Up       0.0.0.0:80->80/tcp

Task-specific containers

Executes a command, then quits

Must be run interactively!

Running drush

docker-compose run drush drushCommand

Creates a new container instace each time!

A container is a process...

...and stays up as long as its process runs

$ docker-compose run drush si  \
    --uri=http://web \
    --root=/var/www/html \
    --db-url=mysql://your_database_user:your_database_password@db/your_database_name -y

You are about to DROP all tables in your 'your_database_name' database. Do you want to continue? (y/n): y
Starting Drupal installation. This takes a while. Consider using the --notify global option.                                                                  [ok]
Installation complete.  User name: admin  User password: jvBdZnmeMt                                                                                           [ok]
Congratulations, you installed Drupal!

$

Dockerizing Existing Sites

Avoid DB dumps!

No really, don't use them.

DB dumps are Tribbles

They quickly become a big problem.

Content in code

Create represtative content with UUID Features

Works better with Behat and automation

I have a DB dump. Sorry. ;_;

Use a client on the host to load it

Best for small databases

$ docker-compose exec db /usr/bin/mysql \
	-u your_database_user \
	-p \
	-C your_database_name < /path/to/your_dump.sql

mariadb container needs the mysql command!

Gigabyte DBs

Increase max_allowed_packet

Import from inside the DB container with a volume

 

 

Cleaning up

Deleting a set

$ docker-compose kill
$ docker-compose rm

Deleting volumes

Not deleted by default!

$ docker-compose rm -v

Images

Snapshot of a container

Created with docker build...

...or downloaded from Hub

Listing images

$ docker images
REPOSITORY           TAG        IMAGE ID         CREATED            SIZE
ridethewhale_web     latest     e716ac87fa15     17 minutes ago     496.4 MB
mariadb              latest     9a0138c02438     6 days ago         391.6 MB
drush/drush          latest     e88ee37fadfd     7 weeks ago        691.9 MB
(Scroll right.)

Deleting images

$ docker rmi $(docker images -q)

Nuke it from orbit

$ docker kill $(docker ps -q)
$ docker rm -v $(docker ps -q -a)
$ docker rmi $(docker images -q)

"It's the only way to be sure."

Nuking Docker for Mac/Win

"Reset to factory defaults" in Docker prefs

Where to go from here

A better CLI container

Use Supervisor for an always-running container

Drupal Console, SASS, Grunt, etc.

Use docker-compose exec for more speed

Read the docs

docs.docker.com

deninet.com/tags/docker-scratch

Use pre-made containers

Lots of options to choose from!

Find the way that fits your style and project

drupal-base and drupal-cli

Bare-bones Drupal containers

Fork it on Github

Dropwhale

Drop-in D8 module dev environment

Pulls, installs Drupal dev internally

Getting Dropwhale

github.com/socketwench/dropwhale

Docker4Drupal

Full-featured: Redis, Memcached, Solr

docker4drupal.org

Build your own!

Per-project is OK!

Contibute to Hub

It's free and anyone can do it

Hub builds your containers, so you don't have to locally

Special thanks

Drupal Association

TEN7

Marc Drummond, Paul Mitchum

and others

Thank you!

 

Read the blog series:
deninet.com/tags/docker-scratch

 

socketwench.github.io/rideTheWhale