Docker is a great tool for managing web servers in easily deployable, configurable, and disposable containers. The gist of it is that much like Vagrant, you can create your own containers or spin up an existing one you find at the Docker Store. Many vendors have official containers, such as for php, wordpress, httpd, mysql, mariadb, etc. Before you build a web app for a certain framework, check to see if someone’s already made a container for it.

It is a great solution for any host OS, but the guest OSes are limited to Linux-based. In the early days of Docker Toolbox, the containers that ran on VirtualBox VMs provided a lot of Docker’s features, but there were some differences with ownership/permissions in that these settings on a Linux host are intrinsically connected to such settings in the container, but for Windows it’s just a totally separate user admin context. In macOS however, there is some overlap in the users/permissions available in the host and container. Also, back in the day, you just had to remember to look in the VirtualBox machine for all of Docker’s volumes and other data. Now you can get a lot more similarity along the lines of Docker for Linux with Docker for Windows and Docker for Mac.

An unfortunate disadvantage to the more recent Docker for Windows is that the Hyper-V extension must be enabled in order to use it. Enabling this extension requires turning on the virtualization setting in your BIOS that is also required to use VirtualBox. However, you will not be able to use both Docker for Windows and still use VirtualBox because it seems to want all the virtualization resources for itself. It’s not all too bad of a situation because it turns out that Hyper-V provides many of the same features as VirtualBox so that you can use it to load OSes from disks or what-have-you into VM environments much like the program you have already known for so long and have loved. The instructions for loading, say, a Linux OS in Hyper-V look pretty straightforward and I can vouch for guaranteed success.

Docker for Mac also uses a native utility in macOS called the Hypervisor framework instead of VirtualBox. The folks at Docker built a tool called Hyperkit for interfacing with this framework. Luckily, you can use can still use VirtualBox while using Docker for Mac, but you’ll definitely want to use all the advantages of Docker for Mac instead of continuing to run Docker in VirtualBox.

Both Docker for Windows and Mac are rather nice tools for working with Docker itself. The user interface for both is very similar. You basically use it to manage the service that needs to run, but it has a lot of other uses. On Windows, be absolutely sure to open the Settings and look at the Shared Drives tab where you can enable which drives can be used for bind-mounted volumes. There will even be a command-line example to show you the format for paths. You used to have to escape all this nonsense in a Window’s file path but now you can just use forward-slashes instead of backslashes.

In Docker for Mac, you’ll want to go to Preferences -> File Sharing to manage which directories and subdirectories can be used for bind-mounted volumes.

Also for both versions, you will definitely want to use Kitematic. It is an excellent companion program that allows great insight into the state of your containers, a quick way to look at their logs, their volumes, their network settings, and plenty more. It beats running a whole bunch of CLI commands anyway. By the way, PHPStorm and Eclipse also have plugins for working with Docker.

Volumes and MySQL

When using a database container like the official mariadb one, you may find yourself wanting to backup the data directory on the host. This is quite easy with a Linux host following their example run command:

$ docker run --name some-mariadb -v /my/own/datadir:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mariadb:tag

This will bind mount /my/own/datadir on the host to to the database data folder in the container. This does not seem to work so well with Docker for Windows and Mac, it seems due to the case-insensitivity of these filesystems.

You can also save the data directory to a volume by way of the VOLUME command in a Dockerfile.

VOLUME ["/var/lib/mysql"]

From what I have read, there just may be a way to create a case-sensitive disk/directory in Windows as you can in macOS. Anyway, if you make mysql write its data files to a Windows case-insensitive directory, it will have trouble creating the necessary files. This isn’t too bad in the end because you probably don’t want to run docker from Windows in a production scenario anyway, which are where the files you’d really want to back up would be.

Nevertheless, if you use the above VOLUME Dockerfile command in Docker for Windows, it will save the database files in a location on the VM that can be found with docker inspect [container-name], as detailed below. Sadly, there is no direct way to access or transfer files between the host and Hyper-V VM. Instead, you can take advantage of how the VM is using a Virtual Network adapter that lets you access the host network so that you can use an SMB client in the MobyLinuxVM to transfer files to the host. A bit cumbersome, but it is possible.

Even though macOS’s filesystem is also case-insensitve, you might experience better luck having the mysql data files written to a folder on a macOS host system. Then again, you might not…

A command like the following, replacing single-quotes with double-quotes on Windows, will show you the folder on the host and the guest OS that pertain to the volume.

docker inspect --format='{{range .Mounts}}docker container path: {{.Source}} Container path: {{.Destination}}{{end}}' database-container-name

Typically with the VOLUME directive in a Dockerfile, it will create a place on the host VM for the volume that is mapped to a location in the container. However, you might also be able to use Kitematic to change the location to somewhere actually on the host rather than in the MobyLinuxVM  or Hyperkit environment.

If, however, you see paths mentioned in docker inspect that are clearly not on the host filesystem, they are probably in the VM made to run Docker. On Windows, you can use this bizarre workaround to access the MobyLinuxVM filesystem. This workaround is no longer working for me though…

To access the Hyperkit VM on macOS requires a series of, frankly, even more ridiculous steps. Hold your breath because it will be a bumpy ride:

First of all, there’s no way I would have figured this all out myself. I thank this StackOverflow question for guidance. Basically, you can initiate a TTY session with the Hyperkit VM by way of the screen command by issuing the following instruction:

screen ~/Library/Containers/com.docker.docker/Data/com.docker.driver.amd64-linux/tty

After issuing the command, you will see a blank screen. Press enter to see the command prompt for the VM. You should be able to use regular GNU commands to look about the filesystem. You will typically find the volume folders in /var/lib/docker/volumes.

Rather than typing that whole tty path out, if you run ls -l on it, you might see, such as in my case, how it is symlinked to a much shorter path, like /dev/ttys001. The screen command can be tricky to work with, but the man pages should help you understand how to go between the TTY session and your regular command-line.

Basic LAMP Stack

The simplest way to get started with an Apache, MySQL, and PHP suite that can all work together is to run a php container and a mysql or mariadb container. We’ll use mariadb in this example.

You should try out the different kinds of images available. All you will really want to do is create a docker network so that the php and mariadb containers can communicate. Both will be running in their separate Linux environments.

Basically, you’ll want to setup your Dockerfiles in a folder call php and mariadb that each at least contain the line FROM php:apache and FROM mariadb, respectively.

I recommend bind-mounting the website files from the host because they will be much easier to work with on the computer you are directly using. You should do what is best for your scenario. Just note that there can be ownership oddities when working with the files on the host versus the container in Docker for Mac and Windows. The docker containers in Linux hosts will rely on whatever UID is set in the host for bind-mounted containters, so you can make everything seamless if you have a user in the container with the same ID.

In this example, we’ll create the network, build the images from the Dockerfiles (which are located in the php and mariadb folders), and then run the containers to always restart. The mariadb container will have environment variables that set the root password, a database user and the database for that user. The php container will be forwarding port 8001 so that a web server on the host can forward requests to itself on that port. If there is a database already on the host on port 3306, port forwarding can help with that situation as well:

docker network create mynetwork
docker build --tag=me/mywebsite php/
docker build --tag=me/mydatabase mariadb/
docker run -p 8001:80 -v c:/my/project:/var/www/html -d --network=mynetwork --restart=always --name=mysite me/mywebsite
docker run -p 3307:3306 -e MYSQL_ROOT_PASSWORD=mypass11 -d --network=mynetwork --restart=always --name=mydb me/mydatabase

Since the containers are on the same docker network, they can access each other with the hostname of the conntainer name, so that the php container can access the database using the hostname mydb. At this point you can issue commands directly to the containers:

docker exec mysite ls -lh

This will confirm that your files are bind-mounted in there. Note whatever follows the container name is interpreted as a command for the container. This only works with words and not symbols that the host command line would interpret for itself, like the vertical bar for example. It is very typical to run bash in the container to obtain a command line interface so that you can run all kinds of commands in that thing:

docker exec -it mysite bash

What is my IP?

You might want to know the IP address with which the guest OS in the container can reach the host. In Docker on a Linux host, there should be a docker0 network interface as revealed by the command ifconfig, the IP address of which is the host.

On Docker for Mac and Windows, it is a little easier. There will be a docker.for.mac.localhost or hostname available from your container that maps to the IP for the host machine. This IP should also be the first node on the Docker subnet set in the Advanced tab of the Docker for Mac Preferences.