Wow it has been a long time since I did a blogpost. However I had this one coming as I got asked about how I used Docker in my current workflow. I used to be – and still am – a big Vagrant fan, however Vagrant is not very suited for development. It is great for managing and provisioning vboxes. But a vbox is sluggish and a resource hog. Vboxes weren’t meant for development, they were meant to run a complete OS on top of yours in a contained fashion.
If you need to run your software on something other than linux, then I still hearty recommend Vagrant for managing those boxes.
If however your website is running on a linux system, then you can leverage the power of Docker.
This blogpost will show you how to quickly setup a Debian environment running Apache, PHP and a database (Postgresql).
Docker Debian Images
head over to https://github.com/NickBelhomme/DockerImages and download and extract the repo.
Alternatively for those who work with git you can
git clone email@example.com:NickBelhomme/DockerImages.git
Once you have the package extracted or cloned just step right into the directory and follow the README.md on how to build the images we need for this post. https://github.com/NickBelhomme/DockerImages/blob/master/README.md
Using the Docker images
You should now be the owner of 3 images.
nickbelhomme/postgres latest 3d09510dab44 2 hours ago 271.3 MB nickbelhomme/apache latest f6eb10d32f1c 2 hours ago 223.6 MB debian wheezy 29853cd4f422 42 hours ago 85.19 MB
You can ignore the hashes, these are random at build time.
So how do we use it?
The first thing we need is 1 additional project or projects if you are a busy bee. In this blog post I have included one demo project.
issue the following command:
cd application docker build -t nickbelhomme/application .
you now end up with 4 images including a
nickbelhomme/application latest 7d4f26227028 33 minutes ago 223.6 MB
If you take a look at the Dockerfile you just issued a build on, you notice we use our previous nickbelhomme/apache image
as a start.
The only thing we apply on top of this image is an extra vhost setting and we enable it.
The Magic of Apache + PHP
Now that you can spawn as much images for each project you have, it is time to at least use one.
docker run --name test1 -P -d nickbelhomme/application /usr/sbin/apachectl -D FOREGROUND
This will start a Docker container with the ports exposed and made public, in this case port 80.
Exposing a port makes it available for inter-container communication and making it public allows your host system to contact the container processes within. We have exposed port 80 in our main apache Dockerfile.
The -d tells the container to run daemonized, which just starts the process – in our case “/usr/sbin/apachectl -D FOREGROUND”
and puts it in the background. Docker -d itself needs to have foreground processes (not deamonized), hence us starting apache in the foreground.
You should just get back a hash. This means your docker container is running. Issue a
to see on which port you can access your website.
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES f718f791dd5e nickbelhomme/application:latest "/usr/sbin/apachectl 2 seconds ago Up 2 seconds 0.0.0.0:49159->80/tcp
this tells us the container its apache port is available through our host on 49159.
Before heading to the browser and issuing a http://local.application.com:49159 we need to add it to our /etc/hosts file.
to get the proper ip address to link to use a ifconfig and you will see the
Docker0 adapter with its IP address. Use that instead of 127.0.0.1.
On Mac or Windows you are using Boot2Docker, so you will need to use the IP of the virtualbox adapter.
Now head over to the browser http://local.application.com:49159 and you will see something such as:
Not Found The requested URL / was not found on this server. Apache/2.2.22 (Debian) Server at local.application.com Port 49159
Success!!! you reached Debian server.
But wait this is not what we wanted. Where is my frigging site???!!!!
Patience is the mother of all things.
Let’s issue ourselves a new container this time mounted with our site:
docker run --name test2 -d -v /home/nick/vhosts/Docker/application/app:/var/www/vhosts/local.application.com -P nickbelhomme/application /usr/sbin/apachectl -D FOREGROUND
With the -v option we mount a local directory inside the container. You must provide absolute paths. The syntax is [localDir:containerDir]. In our application.conf we have set the document root directory to /var/www/vhosts/local.application.com/public so we mount it one level up.
Issuing a docker ps again will show us on which port to connect this time: http://local.application.com:49160
You will see:
SQLSTATE  could not connect to server: Connection refused Is the server running on host "localhost" (::1) and accepting TCP/IP connections on port 5432? could not connect to server: Connection refused Is the server running on host "localhost" (127.0.0.1) and accepting TCP/IP connections on port 5432?
This is normal as we haven’t spawned our database container yet.
Lets do that now!
The magic of the Database Postgresql
docker run -d -P --name postgres nickbelhomme/postgres
As you saw we didn’t have to specify a foregound process with this docker run. That is because we have specified the default command to run when starting the container in our postgres Dockerfile.
once this is up and running we can use it to connect it to our application container
docker run --name application -d --link postgres:postgres -v /home/nick/vhosts/Docker/application/app:/var/www/vhosts/local.application.com -P nickbelhomme/application /usr/sbin/apachectl -D FOREGROUND
We connect 2 containers with the –link param. [containerName:alias]
Now you see why we use the –name param when doing a docker run. More use cases you’ll see later.
Going to the browser again on the new port we will see:
SQLSTATE  FATAL: password authentication failed for user "nick" FATAL: password authentication failed for user "nick"
Which is rather awesome because that means you made a connection. We didn’t setup the user nor the database yet but that we can arrange soon.
Let me first explain why the connection worked out of the box.
Docker uses exposed ports for inter communication it also uses ip addresses between containers. These settings are made available through the –link [containerName:alias] system. The linked container now has access to ALIAS ENVS such as:
POSTGRES_PORT=tcp://10.66.33.4:5432 POSTGRES_PORT_5432_TCP=tcp://10.66.33.4:5432 POSTGRES_PORT_5432_TCP_ADDR=10.66.33.4 POSTGRES_PORT_5432_TCP_PORT=5432 POSTGRES_PORT_5432_TCP_PROTO=tcp POSTGRES_NAME=/grave_elion/postgres
and you can use them in your application. You can find such envs by issuing a
docker run --rm --link postgres:postgres -P nickbelhomme/application env
here you are not running a container in deamonized mode but just issue an env command against the container and when finished you
throw the container away (–rm)
We however want to go inside a postgres container so we can create a user and database.
docker run --rm -t -i --link postgres:postgres nickbelhomme/postgres /bin/bash
This opens an interactive (-i) / pseudo-tty (-t) bash in container throw away mode
Inside we can issue postgresql specific commands
createdb nickdb -h $POSTGRES_PORT_5432_TCP_ADDR -p $POSTGRES_PORT_5432_TCP_PORT -U docker
psql -h $POSTGRES_PORT_5432_TCP_ADDR -p $POSTGRES_PORT_5432_TCP_PORT -d docker -U docker --password
CREATE USER nick WITH PASSWORD '1234'; ALTER database nickdb owner to nick;
Again here you can see the env variables being used. You can also type them yourself, but as long as you use the postgres alias the environment variables naming will remain the same.
The postgresql password for the user docker is docker. We have set this up in our postgres Dockerfile.
You can leave this terminal by leaving psql by typing
and then the terminal by
After you have created the DB and granted the user access it is time to visit the application again.
go to your browser tab of the container named application and refresh.
“no couples table” is displayed on the screen.
Lets populate it.
head over to
and then go back to http::local.application.com:[application PORT]
you should see:
male: nick woman: chanie male: pixel woman: cookie
You do not need to remove the images nor the containers.
To stop working we simply stop the running containers to safe resources.
docker stop postgres docker stop application
docker stop postgres application
Start working on the project again
docker start postgres docker start application
docker start postgres application
each time you stop and start a container you will get new values for POSTGRES_PORT_5432_* so
if you hardcoded the ip instead of using the getenv technique you have to use the trick I showed you on how to get the env variables.
Removing Images and Containers
When you do not need a project or container anymore you can remove containers by name or hash
docker rm postgres docker rm application
Or the image
docker rmi nickbelhomme/application docker rmi nickbelhomme/apache docker rmi nickbelhomme/debian docker rmi nickbelhomme/postgresql
when you remove all the postgres containers you also remove all database data. To see where the data is stored issue a docker inspect postgres and see the volumes part.
Creating a clean slate
remove all containers and all images
docker rm $(docker ps -aq) docker rmi $(docker images -q)
Windows or OSX
Mac and windows use boot2docker.
As stated above in the post, you need to use instead of the docker0 adapter the ip used for the virtualbox machine which is 192.168.59.103. So put that in your hosts file.
Ever since 8 September 2014 guest box additions are included in the Boot2Docker machine so you can find your users folder in /Users for mac and /c/Users for windows.
If you want to mount different folders as well you can use the virtualbox gui to mount additional folders. (vbox/settings/shared folders). Once you have mounted the folders you need you can use the vbox path (docker run -v /c/Users:somePath) to mount it to the container.
Happy Dockering ,
Thanks for taking the time explaining this. It helped grasping the basics.
However I do have a practical question. Assume you do this use case with a composer project. And you build/run your docker to view the website (configured as in you example with a shared volume). What if you update the composer file, and you need to do a composer update? How do you do that on that running docker? Do you have to rebuild it each time? Or is there another approach?
Thanks in advance
If you have setup the project as I outlined your project dir is mounted as a volume in your container. So from your host machine you just issue a “composer update [package]” statement and your project now runs with that update. Exactly the same as if you would manually edit a project file such as the index.php. There is no need to stop and start a container after any code change.