fLuxy.net

Docker tips for local development environments

It is a great idea to use docker and docker-compose to set up development environments near-identical to production. I often use it myself especially for PHP projects.

However there are some annoyances that can arise. This post will elaborate on some of them and their workarounds.

1. docker-compose is too long to type

As normal user - depending on your terminal configuration, modify one of the following files:

~/.profile # if you have source ~/.profile in your ~/.bashrc or ~/.zshrc
~/.bashrc # if you are using bash
~/.zshrc # if you are using zsh

To any of the above files, add the lines:

alias dco=docker-compose
alias dk=docker

Now you can use dk instead of docker and dco instead of docker-compose, example:

dco up -d # instead of docker-compose up -d
dk ps # instead of docker ps

Previously I had suggested using dc as alias for docker-compose. Turns out dc is the name of an existing program - thanks to Sandeep for that (it is not installed on my machine). This might cause issues, so I modified the above to dco instead of dc.

2. Having to use root user or sudo

Docker has a client-server architecture. The heavy lifting of containerization is done by dockerd which runs with root priviledges. It is a daemon that can accept commands from docker clients. By default the daemon listens to a unix socket; access to that socket requires root priviledges. This is why you need to prefix your docker and docker-compose commands with sudo or use an equivalent mechanism. This is not ideal, for a number of reasons.

We can change that by making dockerd listen to a tcp socket - which is permission agnostic. We could even have the server on one machine and the client on another. However for our development machines, it makes sense to use tcp://127.0.0.1:2375 - i.e. limit the connection to same-machine clients over a port 2375.

On most linux distributions docker is started with systemd, we can override the unit file as follows:

Step 1. Tell the server to use tcp

Run the command:

# systemctl edit --full docker

Change the line

ExecStart=/usr/bin/dockerd -H fd://

to

ExecStart=/usr/bin/dockerd -H tcp://127.0.0.1:2375

Step 2. Tell the client we are using tcp

You need to do this only once, and there are many ways to accomplish this, by order of personal preference:

/etc/environment # as root for system-wide effect
~/.profile # normal user, if you have source ~/.profile in your ~/.bashrc or ~/.zshrc
~/.bashrc # normal user, if you are using bash
~/.zshrc # normal user, if you are using zsh

To any of the above files, add the line:

DOCKER_HOST=tcp://127.0.0.1:2375

That’s it!

You can run docker and docker-compose commands as a non-root user!

$ docker ps # no sudo
$ docker-compose up -d # no sudo

Note: There are alternate ways to achieve this as explained in Post-installation steps for Linux on the docker official site - thanks to Jochen for this. However this involves tampering with user accounts on the host computer and can have other consequences; which is why I prefer the tcp approach.

3. Container creates files that the host cannot access (or vice-versa)

Container processes are accessing files via a different user and group than the host processes. Turns out the user name and group name do not actually matter. It is the different user ids.

For example, often docker containers use root (uid=0) or some have www-data.

$ docker run php:fpm id www-data
uid=33(www-data) gid=33(www-data) groups=33(www-data),33(www-data)

We can tell it to use the same uid and gid as the user in the host machine, therefore they will both have the same rights.

Step 1. Pass uid and gid to the container

In your docker-compose.yml file, if you are using image: php:fpm, remove it and replace with the following:

build:
	dockerfile: ./docker/php/Dockerfile
	args:
		- DOCKER_HOST_UID
		- DOCKER_HOST_GID

If you are already using a build directive, just append the args section.

In the above we are telling docker-compose to pass build arguments DOCKER_HOST_UID and DOCKER_HOST_GID with environment variables DOCKER_HOST_UID and DOCKER_HOST_GID as values.

Step 2. Define environment variables

Create a .env file in the same directory as your docker-compose.yml with the following contents:

DOCKER_HOST_UID=1000
DOCKER_HOST_GID=1000

We use a .env file to pass environment variables to docker-compose.

Step 3. Pass the build arguments into the container

In your Dockerfile add the following at the top. Omit the FROM php:fpm statement if you already have a FROM statement present.

FROM php:fpm

ARG DOCKER_HOST_UID=1000
ARG DOCKER_HOST_GID=1000

Here we use the arguments passed at build time with default values 1000.

Step 4. Change the uid and gid of the default user

At the bottom of your Dockerfile add the following line - or append the command to an existing RUN instruction.

RUN groupmod -g ${DOCKER_HOST_GID} www-data && usermod -u ${DOCKER_HOST_UID} www-data

Note: On most linux distributions the default uid is 1000, but you can find out your own by running the command id.

That’s it!

Files generated inside the container are accessible outside, and vice-versa. No need for hacky chmod -R 0777 anymore!

4. Installing libraries for php extensions is a pain

If you are starting off with a php base image, and want to install extensions, you can use the scripts docker-php-ext-configure, docker-php-ext-install, and docker-php-ext-enable. However before doing that, for many extensions you will need install appropriate system libraries first.

Example for GD:

FROM php:7.4-fpm-alpine
RUN apk add --no-cache libpng libpng-dev && docker-php-ext-install gd && apk del libpng-dev

It is not always easy to know which libraries are required by which extensions. This is why the php docker page itself recommends using the install-php-extensions project, you can use it fairly easily as follows:

FROM php:7.4-fpm-alpine
ADD https://raw.githubusercontent.com/mlocati/docker-php-extension-installer/master/install-php-extensions /usr/local/bin/

RUN apk update && apk upgrade && \
	apk add --no-cache bash git openssh && \
	chmod uga+x /usr/local/bin/install-php-extensions && sync && \
	install-php-extensions gd

Surely docker and docker-compose are an important part of any developer’s toolkit and I hope these tricks make using docker and docker-compose less painful for you.