Its extremely important to have default values that you can rely on for local Drupal development, one of those is "localhost". In this blog post we will explore what is required to make our local development environment appear as "localhost".
In our journey migrating to Docker for local dev we found ourselves running into issues with "discovery" of services eg. Solr/Mysql/Memcache.
In our first iteration we used linking, allowing our services to talk to each other, some downsides to this were:
- Tricky to compose an advanced relationship, lets use PHP and PanthomJS as an example:
- PHP needs to know where PhantomJS is running
- PhantomJS needs to know the domain of the site that you are running locally
- Wouldn't it be great if we could just use "localhost" for both of these configurations?
- DNS entries only available within the containers themselves, cannot run utilities outside of the containers eg. Mysql admin tool
With this in mind, we hatched an idea.....
What if we could just use "localhost" for all interactions between all the containers.
- If we wanted to access our local projects Apache, http://localhost (inside and outside of container)
- If we wanted to access our local projects Mailhog, http://localhost:8025 (inside and outside of container)
- If we wanted to access our local projects Solr, http://localhost:8983 (inside and outside of container)
All this can be achieved with Linux Network Namespaces in Docker Compose.
Linux Network Namespaces allow for us to isolate processes into their own "network stacks".
By default, the following happens when a container gets created in Docker:
- Its own Network Namespace is created
- A new network interface is added
- Provided an IP on the default bridge network
However, if a container is created and told to share the same Network Namespace with an existing container, they will both be able to interface with each other on "localhost" or "127.0.0.1".
Here are working examples for both OSX and Linux.
- Mysql and Mail share the PHP containers Network Namespace, giving us "localhost" for "container to container" communication.
- Port mapping for host to container "localhost"
version: "3" services: php: image: previousnext/php:7.1-dev # You will notice that we are forwarding port which do not belong to PHP. # We have to declare them here because these "sidecar" services are sharing # THIS containers network stack. ports: - "80:80" - "3306:3306" - "8025:8025" volumes: - .:/data:cached db: image: mariadb network_mode: service:php mail: image: mailhog/mailhog network_mode: service:php
All containers share the Network Namespace of the users' host, nothing else is required.
version: "3" services: php: image: previousnext/php:7.1-dev # This makes the container run on the same network stack as your # workstation. Meaning that you can interact on "localhost". network_mode: host volumes: - .:/data db: image: mariadb network_mode: host mail: image: mailhog/mailhog network_mode: host
To facilitate this approach we had to make some trade offs:
- We only run 1 project at a time. Only a single process can bind to port 80, 8983 etc.
- Split out the Docker Compose files into 2 separate files, making it simple for each OS can have its own approach.
Since we split out our Docker Compose file to be "per OS" we wanted to make it simple for developers to use these files.
After a couple of internal developers meetings, we came up with some bash aliases that developers only have to setup once.
# If you are on a Mac. alias dc='docker-compose -f docker-compose.osx.yml' # If you are running Linux. alias dc='docker-compose -f docker-compose.linux.yml'
A developer can then run all the usual Docker Compose commands with the shorthand
dc command eg.
dc up -d
This also keeps the command
docker-compose available if a developer is using an external project.
The following solution has also provided us with a consistent configuration fallback for local development.
We leverage this in multiple places in our
settings.php, here is 1 example:
$databases['default']['default']['host'] = getenv("DB_HOST") ?: '127.0.0.1';
- Dev / Stg / Prod environments set the DB_HOST environment variable
- Local is always the fallback (127.0.0.1)
While the solution may have required a deeper knowledge of the Linux Kernel, it has yielded us a much simpler solution for developers.
How have you managed Docker local dev networking? Let me know in the comments below.
Thanks for the write-up! Cleared some confusion up for me!
Interesting stuff.. I have a similar one here https://github.com/heykarthikwithu/docker-drupal
Great Work. But it, doesn't not work when we have multiple containers are interconnected.