Setting up Docker4Drupal/Docker4WordPress with multiple projects on Mac – Redo

Edited 2018-01-24 to switch to .test for local

DNS name docker.for.mac.host.internal shoud be used instead of docker.for.mac.localhost (still valid) for host resolution from containers, since since there is an RFC banning the use of subdomains of localhost. See https://tools.ietf.org/html/draft-west-let-localhost-be-localhost-06.

#####

Since writing Setting up Docker4Drupal with multiple projects on Mac I have discovered a better way. Træfik allows for automatic detection, so you can leave it running in it’s own container. With a few changes to Docker4Drupal and Docker4Wordpress YAML files you can start and stop your development sites without having to reconfigure Træfik.

Træfik container

Create a working folder for Træfik:

mkdir ~/Sites/traefik
cd ~/Sites/traefik

Create wildcard cert files for *.test (If you have not setup Dnsmasq locally I recommend it)

mkdir certs
cd certs
openssl genrsa 2048 > traefik.key
openssl req -new -x509 -nodes -sha1 -days 3650 -key traefik.key > host.cert
#[enter *.test for the Common Name]
openssl x509 -noout -fingerprint -text < traefik.cert > traefik.info
cat traefik.cert traefik.key > traefik.pem
Trust cert

sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain traefik.cert
cd ~/Sites/traefik

Make the Docker-compose.yml file with https support

version: '2'
services:
    traefik:
        image: traefik:1.3.6
        restart: unless-stopped # you can use always if you choose, I perfer unless-stopped locally
        command: '-c /dev/null --web --docker --logLevel=DEBUG --defaultEntryPoints=''http'',''https'' --entryPoints="Name:https Address::443 TLS:/certs/traefik.cert,/certs/traefik.key" --entryPoints="Name:http Address::80"'
        networks:
            - webgateway
        ports:
            - '80:80'
            - '443:443'
            - '8080:8080'
        volumes:
            - '/var/run/docker.sock:/var/run/docker.sock'
            - './certs:/certs'
    portainer:
        image: portainer/portainer
        restart: unless-stopped
        networks:
            - webgateway
        command: '--no-auth'
        volumes:
            - '/var/run/docker.sock:/var/run/docker.sock'
        labels:
            - traefik.backend=portainer
            - traefik.port=9000
            - 'traefik.frontend.rule=Host:portainer.test'
networks:
    webgateway:
        driver: bridge

Start your container. Once it is running it will keep going even after reboot.

docker-compose up -d

Docker4Drupal/Docker4Wordpress

In each project add the following code for each container:

    networks:
      - web

And one global setting:

# Add networks section
networks:
  web:
    external:
      name: traefik_webgateway

Example: (Assumes the folder is called drupal i.e. /Sites/drupal

version: "2"

services:
  mariadb:
    image: wodby/mariadb:10.1-2.3.5
    networks:
      - web
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: drupal
      MYSQL_USER: drupal
      MYSQL_PASSWORD: drupal
#    volumes:
#      - ./mariadb-init:/docker-entrypoint-initdb.d # Place init .sql file(s) here.
#      - /path/to/mariadb/data/on/host:/var/lib/mysql # I want to manage volumes manually.

  php:
# 1. Images with vanilla Drupal – wodby/drupal:[DRUPAL_VERSION]-[PHP_VERSION]-[STABILITY_TAG].
    image: wodby/drupal:8-7.1-3.0.0
    networks:
      - web
    environment:
      PHP_SENDMAIL_PATH: /usr/sbin/sendmail -t -i -S mailhog:1025
      PHP_FPM_CLEAR_ENV: "no"
      DB_HOST: mariadb
      DB_USER: drupal
      DB_PASSWORD: drupal
      DB_NAME: drupal
      DB_DRIVER: mysql
#      PHP_XDEBUG: 1
#      PHP_XDEBUG_DEFAULT_ENABLE: 1
#      PHP_XDEBUG_REMOTE_CONNECT_BACK: 0
#      PHP_XDEBUG_REMOTE_HOST: "10.254.254.254"
#      PHP_XDEBUG_PROFILER_OUTPUT_DIR: /mnt/files/xdebug/profiler
#      PHP_XDEBUG_TRACE_OUTPUT_DIR: /mnt/files/xdebug/traces
    volumes:
      - codebase:/var/www/html
## Options for macOS users (https://docker4drupal.readthedocs.io/en/latest/macos)
#      - codebase:/var/www/html:cached # User-guided caching
#      - docker-sync:/var/www/html # Docker-sync
## For Xdebug profiler files
#      - files:/mnt/files

  nginx:
# wodby/drupal-nginx:[DRUPAL_VERSION]-[NGINX_VERSION]-[STABILITY_TAG].
    image: wodby/drupal-nginx:8-1.13-3.0.1
    networks:
      - web
    depends_on:
      - php
    environment:
      NGINX_STATIC_CONTENT_OPEN_FILE_CACHE: "off"
      NGINX_ERROR_LOG_LEVEL: debug
      NGINX_BACKEND_HOST: php
      NGINX_SERVER_ROOT: /var/www/html/web
    volumes:
      - codebase:/var/www/html
# Options for macOS users (https://docker4drupal.readthedocs.io/en/latest/macos)
#      - codebase:/var/www/html:cached # User-guided caching
#      - docker-sync:/var/www/html # Docker-sync
    labels:
      - 'traefik.backend=drupal_nginx_1'
      - 'traefik.port=80'
      - 'traefik.frontend.rule=Host:drupal.test'

  mailhog:
    image: mailhog/mailhog
    networks:
      - web
    labels:
      - 'traefik.backend=drupal_mailhog_1'
      - 'traefik.port=8025'
      - 'traefik.frontend.rule=Host:mailhog.drupal.test'

#   portainer: is not needed in each project

# traefik: is not needed in each project

# Add networks section
networks:
  web:
    external:
      name: traefik_webgateway

volumes:
  codebase:
   docker-sync:
     external: true

Using DNSmasq locally will allow Safari and Firefox to work for the test sites.

EDIT: Download docker-compose.yml on GitHub

Using Dnsmasq for local development on macOS

Edited 2018-01-24 to switch to .test for local

DNS name docker.for.mac.host.internal shoud be used instead of docker.for.mac.localhost (still valid) for host resolution from containers, since since there is an RFC banning the use of subdomains of localhost. See https://tools.ietf.org/html/draft-west-let-localhost-be-localhost-06.

#####

There is a known issue with Docker for Mac, “.localhost DNS doesn’t resolve in browsers other than Chrome“. Docker4Drupal talks about it here and Træfik has a thread here. I found using Dnsmasq a better solution for me than editing the hosts file for each project I work with.


Adapted and edited from Using Dnsmasq for local development on OS X – Thomas Sutton

Most web developers will be familiar with the process of updating your /etc/hosts file to direct traffic for coolproject.test to 127.0.0.1. Most will also be familiar with the problems of this approach:

  • it requires a configuration change every time you add or remove a project; and
  • it requires administration access to make the change.

Installing a local DNS server like Dnsmasq and configuring your system to use that server can make these configuration changes a thing of the past. In this post, I’ll run through the process of:

  1. Installing Dnsmasq on OS X.
  2. Configuring Dnsmasq to respond to all .test  requests with 127.0.0.1.
  3. Configure OS X to send all .test  requests requests to Dnsmasq.

Before we get started, I should give you a warning: these instructions show you how to install new system software and change your system configuration. Like all such changes, you should not proceed unless you are confident you have understood them and that you can reverse the changes if needed.

Installing Dnsmasq

To quote the Dnsmasq project home page

Dnsmasq is a lightweight, easy to configure DNS forwarder and DHCP server is targeted at home networks.

There are plenty of ways to install Dnsmasq but my favourite (on OS X) is to use the Homebrew package manager. Installing Homebrew is fairly simple but beyond my scope here.

Once you have Homebrew installed, using it to install Dnsmasq is easy:

# Update your homebrew installation
brew up
# Install dnsmasq
brew install dnsmasq

The installation process will output several commands that you can use to start Dnsmasq automatically with a default configuration. I used the following commands but you should use whichever commandsbrew tells you to:

# Copy the default configuration file.
cp $(brew list dnsmasq | grep /dnsmasq.conf.example$) /usr/local/etc/dnsmasq.conf
# Copy the daemon configuration file into place.
sudo cp $(brew list dnsmasq | grep /homebrew.mxcl.dnsmasq.plist$) /Library/LaunchDaemons/
# Start Dnsmasq automatically.
sudo launchctl load /Library/LaunchDaemons/homebrew.mxcl.dnsmasq.plist

Configuring Dnsmasq

Now that you have Dnsmasq installed and running, it’s time to configure it! The configuration file lives at /usr/local/etc/dnsmasq.conf by default, so open this file in your favourite editor.

One the many, many things that Dnsmasq can do is compare DNS requests against a database of patterns and use these to determine the correct response. I use this functionality to match any request which ends in .test  and send 127.0.0.1 in response. The Dnsmasq configuration directive to do this is very simple:

address=/test/127.0.0.1

Insert this into your/usr/local/etc/dnsmasq.conffile (I put it near the exampleaddress=/double-click.net/127.0.0.1 entry just to keep them all together) and save the file.

You may need to restart Dnsmasq to get it to recognise this change. Restarting Dnsmasq is the same as any other service running under launchd:

sudo launchctl stop homebrew.mxcl.dnsmasq
sudo launchctl start homebrew.mxcl.dnsmasq

You can test Dnsmasq by sending it a DNS query using thedigutility. Pick a name ending intest and use dig to query your new DNS server:

dig testing.testing.one.two.three.test @127.0.0.1

You should get back a response something like:

;; ANSWER SECTION:
testing.testing.one.two.three.test. 0 IN A       127.0.0.1

Configuring OS X

Send only .test  queries to Dnsmasq.

Most UNIX-like operating systems have a configuration file called /etc/resolv.conf which controls the way DNS queries are performed, including the default server to use for DNS queries (this is the setting that gets set automatically when you connect to a network or change your DNS server/s in System Preferences).

OS X also allows you to configure additional resolvers by creating configuration files in the /etc/resolver/ directory. This directory probably won’t exist on your system, so your first step should be to create it:

sudo mkdir -p /etc/resolver

Now you should create a new file in this directory for each resolver you want to configure. Each resolver corresponds – roughly and for our purposes – to a top-level domain like ourdev. There a number of details you can configure for each resolver but I generally only bother with two:

  • the name of the resolver (which corresponds to the domain name to be resolved); and
  • the DNS server to be used.

For more information about these files, see the resolver(5) manual page:

Create a new file with the same name as your new top-level domain (I’m using test, recall) in the /etc/resolver/ directory and add a nameserver to it by running the following commands:

sudo tee /etc/resolver/test \>/dev/null \<\<EOF
nameserver 127.0.0.1
EOF

Heretestis the top-level domain name that I've configured Dnsmasq to respond to and127.0.0.1 is the IP address of the server to use.

Once you’ve created this file, OS X will automatically read it and you’re done!

Testing

Testing you new configuration is easy; just use ping check that you can now resolve some DNS names in your new top-level domain:

# Make sure you haven't broken your DNS.
ping -c 1 www.google.com
# Check that .dev names work
ping -c 1 this.is.a.test.test
ping -c 1 iam.the.walrus.test

You should see results that mention the IP address in your Dnsmasq configuration like this:

PING iam.the.walrus.test (127.0.0.1): 56 data bytes

You can now just make up new DNS names under.test whenever you please. Congratulations!

Setting up docker4drupal with multiple projects on Mac

I have recently started testing and using docker4drupal. (I also use DrupalVM and vagrant; but that’s another post.) I setup to be quick and easy.

Setup

You will need:

Docker Compose Spacely Sprockets (new site)

~/Sites/spacely-sprokets/docker-compose.yml

version: "2"

services:
  mariadb:
    image: wodby/mariadb:10.1-2.3.3
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: drupal
      MYSQL_USER: drupal
      MYSQL_PASSWORD: drupal
 
  php:
# 1. Images with vanilla Drupal – wodby/drupal:[DRUPAL_VERSION]-[PHP_VERSION]-[STABILITY_TAG].
    image: wodby/drupal:8-7.1-2.4.4
    environment:
      PHP_SENDMAIL_PATH: /usr/sbin/sendmail -t -i -S mailhog:1025
      DB_HOST: mariadb
      DB_USER: drupal
      DB_PASSWORD: drupal
      DB_NAME: drupal
      DB_DRIVER: mysql
      # PHP_XDEBUG: 1
      # PHP_XDEBUG_DEFAULT_ENABLE: 1
      # PHP_XDEBUG_REMOTE_CONNECT_BACK: 0         # This is needed to respect remote.host setting below
      # PHP_XDEBUG_REMOTE_HOST: "10.254.254.254"  # You will also need to 'sudo ifconfig lo0 alias 10.254.254.254'
    volumes:
       - spacely-sprokets-sync:/var/www/html # Docker-sync

  nginx:
# wodby/drupal-nginx:[DRUPAL_VERSION]-[NGINX_VERSION]-[STABILITY_TAG].
    image: wodby/drupal-nginx:8-1.13-2.4.2
    depends_on:
      - php
    environment:
      NGINX_STATIC_CONTENT_OPEN_FILE_CACHE: "off"
      NGINX_ERROR_LOG_LEVEL: debug
      NGINX_BACKEND_HOST: php
      NGINX_SERVER_ROOT: /var/www/html/web
    volumes:
       - spacely-sprokets-sync:/var/www/html # Docker-sync
    labels:
      - 'traefik.backend=nginx'
      - 'traefik.port=80'
      - 'traefik.frontend.rule=Host:spacely-sprokets.docker.localhost'

   mailhog:
    image: mailhog/mailhog
    labels:
      - 'traefik.backend=mailhog'
      - 'traefik.port=8025'
      - 'traefik.frontend.rule=Host:mailhog.spacely-sprokets.docker.localhost'
 
volumes:
# Docker-sync for macOS users
   spacely-sprokets-sync:
     external: true

Docker Sync Spacely Sprockets

~/Sites/spacely-sprokets/docker-sync.yml

version: “2”

syncs:
  spacely-sprokets-sync:
src: ‘./‘
sync_userid: ‘82’
sync_excludes: [‘.gitignore’, ‘.git/‘, ‘.idea/‘]

Docker Cogswell Cogs (existing code)

~/Sites/cogswell-cogs/docker-compose.yml

version: "2"

services:
  mariadb:
    image: wodby/mariadb:10.1-2.3.3
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: drupal
      MYSQL_USER: drupal
      MYSQL_PASSWORD: drupal
      volumes:
       - ./mariadb-init:/docker-entrypoint-initdb.d # Place init .sql file(s) here.

  php:
# 2. Images without Drupal – wodby/drupal-php:[PHP_VERSION]-[STABILITY_TAG].
     image: wodby/drupal-php:5.6-2.4.3
    environment:
      PHP_SENDMAIL_PATH: /usr/sbin/sendmail -t -i -S mailhog:1025
      DB_HOST: mariadb
      DB_USER: drupal
      DB_PASSWORD: drupal
      DB_NAME: drupal
      DB_DRIVER: mysql
      # PHP_XDEBUG: 1
      # PHP_XDEBUG_DEFAULT_ENABLE: 1
      # PHP_XDEBUG_REMOTE_CONNECT_BACK: 0         # This is needed to respect remote.host setting below
      # PHP_XDEBUG_REMOTE_HOST: "10.254.254.254"  # You will also need to 'sudo ifconfig lo0 alias 10.254.254.254'
    volumes:
       - cogswell-cogs-sync:/var/www/html # Docker-sync

   apache:
     image: wodby/drupal-apache:2.4-1.0.2
    depends_on:
      - php
     environment:
       APACHE_LOG_LEVEL: debug
       APACHE_BACKEND_HOST: php
       APACHE_SERVER_ROOT: /var/www/html/www
     volumes:
    - cogswell-cogs-sync-sync:/var/www/html # Docker-sync
     labels:
       - 'traefik.backend=apache'
       - 'traefik.port=80'
       - 'traefik.frontend.rule=Host:drupal.docker.localhost'

  mailhog:
    image: mailhog/mailhog
    labels:
      - 'traefik.backend=mailhog'
      - 'traefik.port=8025'
      - 'traefik.frontend.rule=Host:mailhog.drupal.docker.localhost'

volumes:
 # Docker-sync for macOS users
   cogswell-cogs-sync:
     external: true

Docker Sync Cogswell Cogs

~/Sites/cogswell-cogs/docker-sync.yml

version: “2”

syncs:
  cogswell-cogs-sync:
src: ‘./‘
sync_userid: ‘82’
sync_excludes: [‘.gitignore’, ‘.git/‘, ‘.idea/‘]

Traefik.yml

~/Sites/traefik.yml


version: ‘2’

services:
  traefik:
    image: traefik
    restart: unless-stopped
    command: -c /dev/null –web –docker –logLevel=DEBUG
    networks:
        - spacelysprokets
        - cogswellcogs
        - sites
     ports:
        - ‘80:80’
        - ‘8080:8080’
   volumes:
       - /var/run/docker.sock:/var/run/docker.sock
  portainer:
        image: portainer/portainer
        command: '--no-auth'
        volumes:
            - '/var/run/docker.sock:/var/run/docker.sock'
        labels:
            - traefik.backend=portainer
            - traefik.port=9000
            - 'traefik.frontend.rule=Host:portainer.docker.localhost'
networks:
  spacelysprokets:
    external: 
      name: spacelysprokets_default
  cogswellcogs:
    external:
      name: cogswellcogs_default
  sites:
    external:
      name: sites_default

Commands

Start Cogswell Cogs

cd ~/Sites/cogswell-cogs
docker-sync-stack start

Start Spacely Sprokets

cd ~/Sites/spacely-sprokets
docker-sync-stack start

Start traefik and portainer.

cd ~/Sites
docker-compose -f traefik.yml up -d

Shell access to the site

cd ~/Sites/cogswell-cogs
docker-compose exec --user=82 php sh

Stop docker

cd ~/Sites/cogswell-cogs
docker-compose -f ../traefik.yml stop
docker-compose stop
docker-sync stop