Drupalcamp London 2018

Dockerize your Drupal environments

Fran García-Linares

March 2018

About me

fran.garcia@amazeelabs.com

Fran García-Linares, Drupal web developer

fjgarlin@gmail.com

https://www.drupal.org/u/fjgarlin

We are

HIRING

Agenda (45min)

1

2

3

4

5

Intro (5 min)

Docker set up (20~25 min)

Real-life example (5 min)

Q & A

Pros and cons (5 min)

Intro

Environments

 

Depends on:

- Size of project

- Size of company

- Level of knowledge/skills of the team

 

Options:

- Production

- Development > production

- Local > development > production

- Local > development > staging > production

Options for "local"

  • (M/W/L)AMP
  • Vagrant
  • Cloud + SFTP
  • Valet (laravel)
  • PHP built-in server
  • ...
  • Docker
    • ​amazee.io
    • docker4drupal
    • Lagoon
    • Custom docker images

With docker you can get an identical set up to the production* server... i-d-e-n-t-i-c-a-l

* docker in production? why not :-)

Do I need to know docker to use docker?

My day to day

I work in maintenance and support... how does docker help me with this?

  • Quick set up / Task switching
    • Normal day: 3~6 different projects
    • Normal week: 15~20 different projects
  • Issue / bug replication
    • Exact set up
    • Exact content
    • Exact files
    • ...
  • Computer resources
  • Common set up across the team

Docker set up

useful command aliases

  • dsql
  • dfiles
  • dssh
  • dsshroot
  • dup
  • dstop
  • ddown
  • dps
function dsql() {
  drush sql-sync $1 default
}
function dfiles() {
  drush rsync $1:%files default:%files
}
alias ddown='docker-compose down'
alias dps='docker ps --format '\''table {{.Names}}\t{{.Status}}'\'''
alias dssh='docker-compose exec --user drupal drupal bash'
alias dsshroot='docker-compose exec --user root drupal bash'
alias dstop='docker-compose stop'
alias dstopall='docker stop $(docker ps -a -q)'
alias dup='docker-compose up -d'

Docker set up

important drush files

drushrc.php

settings.php

// Append this if the server variables are set up in all environments.
// You can safely commit settings.php to git.
if(getenv('ENV_DB_NAME')) {
  $databases['default']['default'] = array(
    'driver' => 'mysql',
    'database' => getenv('ENV_DB_NAME'),
    'username' => getenv('ENV_DB_USERNAME'),
    'password' => getenv('ENV_DB_PASSWORD'),
    'host' => getenv('ENV_DB_HOST'),
    'port' => getenv('ENV_DB_PORT'),
    'prefix' => '',
  );
}

### amazee.io Base URL
if (getenv('ENV_BASE_URL')) {
  $base_url = getenv('ENV_BASE_URL');
}
if (getenv('ENV_SITE_URL')) {
  $options['uri'] = 'http://' . getenv('ENV_SITE_URL');
}

$command_specific['rsync'] = array('verbose' => TRUE);
$aliases['local'] = array(
  'root' => '/path/to/drupal/root',
  'uri'  => 'yoursite.localhost',
  'path-aliases' => array(
    '%dump-dir' => '/tmp',
  ),
);

$aliases['dev'] = array (
  'uri' => 'yoursite.dev',
  'root' => '/path/to/drupal/root',
  'remote-user' => 'ssh-username',
  'remote-host' => 'ssh-host',
  'ssh-options'  => '-p 2222',  // To change the default port on remote server
  'path-aliases' => array(
    '%dump-dir' => '/tmp',
  ),
  'source-command-specific' => array (
    ...
  ),
  // No need to modify the following settings
  'command-specific' => array (
    ...
  ),
);

$aliases['prod'] = array (
  // This is the full site alias name from which we inherit its config.
  'parent' => '@yoursite.dev',  
  'uri' => 'yoursite.com',
  'root' => '/path/to/drupal/root',
  'remote-user' => 'ssh-user',
  'remote-host' => 'ssh-host',
);

Docker set up

syncing DB and persistence in docker

Syncing DB

$ drush sa
@local
@dev
@prod

$ dsql @prod

$ drush sql-sync @prod @local    // NOT THE OTHER WAY AROUND!! dsql...

DB persistence in docker... aka volumes

    volumes:
      - .:/var/www/drupal/public_html
    ports:
      - "3306"

Add these lines (or similar) somewhere in your docker-compose.yml file:

Docker set up

syncing files or use stage_file_proxy

Syncing files

$ drush sa
@local
@dev
@prod

$ dfiles @prod

$ drush rsync @prod:%files @local:%files    // NOT THE OTHER WAY AROUND!! dfiles...
// settings.php
$conf['stage_file_proxy_origin'] = "http://prodsite.url";

Enable the module on local, make sure that sites/default/files is writable and then:

Docker set up

syncing SOLR (Search API)

Syncing SOLR configuration

// docker-compose.yml
...
services:
  ...
  solr:
    image: solr
    ports:
     - "8983:8983"
    volumes:
      - ./docker/solr/conf:/etc/solr/conf/drupal/conf
    ...

Then re-index, that's it!

Docker set up

other integrations

redis

// docker-compose.yml
...
services:
  drupal:
    ...
    environment:
      ...
      ENV_REDIS_HOST: redis
      ENV_REDIS_PORT: '6379'
    ...
    links:
      - redis
  redis:
    image: redis:alpine
    network_mode: bridge

emails

// docker-compose.yml
...
services:
  ...
   mailhog:
    image: mailhog/mailhog
    ...

varnish

memcached

node

blackfire

...

Docker set up

settings

settings.php (committed to git)

// Base URL
if (getenv('AMAZEEIO_SITE_URL')) {
  $base_url = 'http://' . getenv('AMAZEEIO_SITE_URL');
}

// AMAZEE.IO Redis settings
if (getenv('AMAZEEIO_REDIS_HOST') && getenv('AMAZEEIO_REDIS_PORT')) {
  $conf['redis_client_interface'] = 'Predis';
  $conf['redis_client_host'] = getenv('AMAZEEIO_REDIS_HOST');
  $conf['redis_client_port'] = getenv('AMAZEEIO_REDIS_PORT');
}

// AMAZEE.IO Database connection
if(getenv('AMAZEEIO_DB_NAME')){
  $databases['default']['default'] = array(
    ...
  );
}

// AMAZEE.IO SOLR connection
if(getenv('AMAZEEIO_SOLR_HOST') && getenv('AMAZEEIO_SOLR_PORT')){
  // Override search API server settings fetched from default configuration.
  $conf['search_api_override_mode'] = 'load';
  $conf['search_api_override_servers'] = array(
    'solr' => array(
      'name' => 'Amazee.io Solr - Environment:' . getenv('AMAZEEIO_SITE_ENVIRONMENT'),
      'options' => array(
        'host' => getenv('AMAZEEIO_SOLR_HOST'),
        'port' => getenv('AMAZEEIO_SOLR_PORT'),
        'path' => '/solr/'.getenv('AMAZEEIO_SITENAME').'/',
        'excerpt' => 0,
        ...
        'http_method' => 'POST',
      ),
    ),
  );
}

settings.all.php

// Settings for all environments.
if (file_exists(__DIR__ . '/settings.all.php')) {
  include __DIR__ . '/settings.all.php';
}

// Environment specific settings files.
if(getenv('AMAZEEIO_SITE_ENVIRONMENT')){
  if (file_exists(__DIR__ . '/settings.' . getenv('AMAZEEIO_SITE_ENVIRONMENT') . '.php')) {
    include __DIR__ . '/settings.' . getenv('AMAZEEIO_SITE_ENVIRONMENT') . '.php';
  }
}

// Last: server's specific settings files. 
// NOTE: Include this in .gitignore
if (file_exists(__DIR__ . '/settings.local.php')) {
  include __DIR__ . '/settings.local.php';
}

settings.development.php

// Error messages to display: All messages
$conf['error_level'] = 2;

// Google Analytics.
$conf['googleanalytics_account'] = 'UA-XXXXXXXX-Y';

//Theme Debug
$conf['theme_debug'] = TRUE;

$conf['preprocess_css'] = FALSE;
$conf['preprocess_js'] = FALSE;

$conf['cache'] = 0;
$conf['cache_lifetime'] = 0;

// Stage File Proxy, so that files are loaded from Production
$conf['stage_file_proxy_origin'] = "http://prod.url";

// Test keys for other services
// ie: Salesforce, Mandrill, etc.

Docker set up

D7 vs D8 (CMI)

D7

Typical workflow:

  • git clone repo folder
  • cd folder
    • git submodule update --init
    • git submodules sync
    • dup
    • dssh
      • dsql @prod
      • mkdir sites/default/files
      • chmod -R 777 sites/default/files
      • drush -y en stage_file_proxy
      • drush cc all
      • drush uli

D8

Typical workflow:

  • git clone repo folder
  • cd folder
    • dup
    • dssh
      • composer install
      • dsql @prod OR drush -y cim
      • dfiles @prod
      • drush cr
      • drush uli

We'll see a "live" demo in 2 slides :-)

Docker set up

docker-compose.yml

This file is almost all you need to work with a dockerized environment

version: '2'
services:
  drupal:
    # Choose the URL and hostname for this Docker Container
    hostname: &hostname changeme.docker.amazee.io

    environment:
      WEBROOT: web
      APC: 1
      VIRTUAL_HOST: *hostname
    image: amazeeio/drupal:php70-basic
    volumes:
      - .:/var/www/drupal/public_html
    volumes_from:
      - container:amazeeio-ssh-agent
    container_name: *hostname
    network_mode: bridge
    ports:
      - "3306"

AMAZEE.IO

version: '2'
services:
  cli:
    build:
      context: .
      dockerfile: Dockerfile.builder
    image: builder
    ...
  nginx:
    networks:
      - amazeeio-network
      - default
    build:
      context: .
      dockerfile: Dockerfile.nginx
    ...
  php:
    build:
      context: .
      dockerfile: Dockerfile.php
    ...
  mariadb:
    image: amazeeio/centos7-mariadb10-drupal
    labels:
      lagoon.type: mariadb
  redis:
    image: amazeeio/redis
    labels:
      lagoon.type: redis
  solr:
    image: amazeeio/solr:6.6-drupal
    labels:
      lagoon.type: solr
    ports:
    - "8983"
  varnish:
    image: amazeeio/varnish-drupal
    networks:
      - amazeeio-network
      - default
    ...

networks:
  amazeeio-network:
    external: true

LAGOON

version: "2"
services:
  mariadb:
    image: wodby/mariadb:10.2-3.0.2
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: drupal
    ...

  php:
    image: wodby/drupal:8-7.1-3.3.2
#    image: wodby/drupal:7-7.0-3.3.2
    environment:
      PHP_SENDMAIL_PATH: /usr/sbin/sendmail -t -i -S mailhog:1025
      DB_HOST: mariadb
      DB_USER: drupal
    ...

  nginx:
    image: wodby/drupal-nginx:8-1.13-3.0.2
#    image: wodby/drupal-nginx:7-1.13-3.0.2
    depends_on:
      - php
    environment:
    ...
    volumes:
      - codebase:/var/www/html
#  apache:
#  varnish:
#  redis:
#  pma:
#  solr:
  mailhog:
#  nodejs:
#  node:
#  memcached:
#  rsyslog:
#  blackfire:
  traefik:
...
volumes:
  codebase:

DOCKER4DRUPAL

Real-life example

demo time

D7

Typical workflow (from scratch):

  • git clone repo folder
  • cd folder
    • git submodule update --init
    • git submodules sync
    • dup
    • dssh
      • dsql @prod
      • mkdir sites/default/files
      • chmod -R 777 sites/default/files
      • drush -y en stage_file_proxy
      • drush cc all
      • drush uli

Pros & Cons

Pros

Cons

  • Exact copy of prod
  • Really fast
  • Computer resources
    • Multiple containers won't require too much
  • Portable
  • ...
  • Initial configuration (easier than alternatives)
  • Volatility?
    • ​Volumes
    • Redundancy
  • ​Learning?

Docker in production for Drupal...

a much more advanced talk: https://www.youtube.com/watch?v=3RnZPrjvoqo

Q & A

https://slides.com/fjgarlin

Dockerize your Drupal environments // Drupalcamp London 2018

By Fran García-Linares

Dockerize your Drupal environments // Drupalcamp London 2018

  • 2,854