Docker
last updated 2019-12
note - vm and container are used below interchangeably
installation and verification
# install Docker
$ curl -fsSL https://get.docker.com/ | sh
# add your-user to docker group to be able to run docker as non-root
$ usermod -aG docker your-user
# you'll need to log out and in after this, or run all docker commands as root
# verify the installation
$ docker run hello-world
# this should download image from docker hub, and print "Hello from Docker." message
as a rule of thumb: docker is the "super-system" used to manage docker containers (and more). docker compose does the same tasks as docker, but only on containers defined in the docker-compose.yml file in current directory. so, docker ps -a will list all containers, docker compose ps will list info about all containers from the local docker-compose.yml file
update 2026 - Compose is a docker plugin now, docker compose … - the old standalone docker-compose (v1) is dead. in docker-compose.yml, the version: key is ignored, and links: aren't needed - services on the same compose network reach each other by service name anyway
if something goes wrong, check
$ docker compose logs
or
$ docker logs container-id
stack in separate containers
let's create a few containers to hold different parts of the stack:
- nginx
- php-fpm
- mysql
- data volume for mysql
- kyoto tycoon with python
- data volume for kyoto tycoon
first, create the config file for docker compose, called docker-compose.yml
nginx:
# based on latest nginx image
image: nginx:latest
ports:
# map vm's port 80 to local port 8080
- 8080:80
now we have defined our first image, let's get it running:
$ docker compose up -d
# you should be able to reach the nginx on localhost:8080 now;
# you can always check running containers, incl. ports map, with
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e795e45c3e6b nginx:latest "nginx -g 'daemon off" 24 hours ago Up 24 hours 443/tcp, 0.0.0.0:8080->80/tcp dockertut_nginx_1
# using container ID or name from `docker ps`, you can get lots of useful info with
$ docker inspect {container-id|name}
adding php-fpm
let's now add a VM for php-fpm, and edit the docker-compose.yml like this:
nginx:
build:
# don't use image, build based on ./docker/nginx/Dockerfile
./docker/nginx/
ports:
# vm's port 80 will be available as 8080 on localhost
- 8080:80
links:
# has access to 'php' vm
- php
volumes:
# mount current directory as /var/www/html inside the container
- .:/var/www/html
php:
image: php:5.5-fpm
expose:
# expose port 9000 only to other vm's, not host machine like the 'ports' command
- 9000
volumes:
- .:/var/www/html
now the nginx image is based on the Dockerfile in the ./docker/nginx/ folder, which should look like this:
FROM nginx:latest
COPY ./default.conf /etc/nginx/conf.d/default.conf
see the official Dockerfile reference for details
this will build the vm from the nginx:latest image, and copy ./docker/nginx/default.conf to /etc/nginx/conf.d/ in the vm. create this file with the following content:
server {
listen 80 default_server;
root /var/www/html;
index index.html index.php;
charset utf-8;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
access_log off;
error_log /var/log/nginx/error.log error;
sendfile off;
client_max_body_size 100m;
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass php:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_intercept_errors off;
fastcgi_buffer_size 16k;
fastcgi_buffers 4 16k;
}
location ~ /\.ht {
deny all;
}
}
note that root /var/www/html; will actually use the mount of our local directory
also note fastcgi_pass php:9000; that uses the link created using the links: key in the docker-compose.yml
now, let's just add index.php in the root folder (where docker-compose.yml resides):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Hello World!</title>
</head>
<body>
<?php echo "this is php!"; ?>
</body>
</html>
let's start the images; this time, the php image should be pulled as well:
$ docker compose up -d
Pulling php (php:5.5-fpm)...
5.5-fpm: Pulling from library/php
efd26ecc9548: Already exists <-- the nginx image, already pulled
a3ed95caeb02: Download complete
...
if you get a "Service 'nginx' needs to be built, but --no-build was passed." message, run docker compose build and again docker compose up -d. verify all is OK and started:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
fd04b796e34f dockertut_nginx "nginx -g 'daemon off" 4 seconds ago Up 4 seconds 443/tcp, 0.0.0.0:8080->80/tcp dockertut_nginx_1
402fb35b762e php:5.5-fpm "php-fpm" 5 seconds ago Up 4 seconds 9000/tcp dockertut_php_1
note that nginx's image name has changed, since we have built a new one
now, when you open localhost:8080, you should get the "this is php!" message. open the index.php file and edit it - the change should be reflected immediately. sweet!
data volumes
both the nginx and php vm's now use the local folder through the volumes directive
better yet is to have a separate container. we'll build this container based on some image we're already using (to avoid having yet another image downloaded), but it won't be running - it'll just sit there, collecting data. change docker-compose.yml like this:
nginx:
build:
./docker/nginx/
ports:
- 8080:80
links:
- php
volumes_from:
- app
php:
image: php:5.5-fpm
expose:
- 9000
volumes_from:
- app
app:
# it's good practice to "reuse" some image, not to have yet another one pulled
image: php:5.5-fpm
volumes:
- .:/var/www/html
# container won't run - it'll execute `true` and sit there, collecting data
command: "true"
let's check all is OK:
$ docker compose up -d
Creating dockertut_app_1...
Recreating dockertut_php_1...
Recreating dockertut_nginx_1...
# list all containers (note that 'app' container is not listed in `ps`, since it's not running):
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
af0110364abf dockertut_nginx "nginx -g 'daemon off" 2 minutes ago Up 2 minutes 443/tcp, 0.0.0.0:8080->80/tcp dockertut_nginx_1
94a1c3d317dd php:5.5-fpm "php-fpm" 2 minutes ago Up 2 minutes 9000/tcp dockertut_php_1
861cca283ceb php:5.5-fpm "true" 2 minutes ago Exited (0) 2 minutes ago dockertut_app_1
and check that localhost:8080 is still responding
adding MySQL
update the 'php' section:
php:
build:
./docker/php/
expose:
- 9000
links:
- mysql
volumes_from:
- app
and add the following:
mysql:
image: mysql:latest
volumes_from:
- mysqldata
# set environment variables
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: test
MYSQL_USER: test
MYSQL_PASSWORD: test
mysqldata:
image: mysql:latest
volumes:
# the /var/lib/mysql will be present in container, and "somewhere" on host
- /var/lib/mysql
command: "true"
and create ./docker/php/Dockerfile:
FROM php:5.5-fpm
RUN docker-php-ext-install pdo_mysql
now we're all set to docker compose build and docker compose up -d. let's check what containers we have now:
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e43265be9680 dockertut_nginx "nginx -g 'daemon off" 16 seconds ago Up 14 seconds 443/tcp, 0.0.0.0:8080->80/tcp dockertut_nginx_1
48cd3b544fb1 dockertut_php "php-fpm" 19 seconds ago Up 17 seconds 9000/tcp dockertut_php_1
0e4f40d9ae8b mysql:latest "docker-entrypoint.sh" 21 seconds ago Up 20 seconds 3306/tcp dockertut_mysql_1
963440e29d89 mysql:latest "docker-entrypoint.sh" 22 seconds ago Exited (0) 20 seconds ago dockertut_mysqldata_1
1458644df104 php:5.5-fpm "true" 4 minutes ago Exited (0) 4 minutes ago dockertut_app_1
now that all is set up, we can check where docker mounted the folder on host that is seen as /var/lib/mysql in the mysqldata instance:
$ docker inspect dockertut_mysqldata_1
...
"Mounts": [
{
"Name": "d1352deaff99b25ee68bc53c07150b12b5eb7028b6182f1c97f71f7e21c8ee1d",
"Source": "/var/lib/docker/volumes/d1352deaff99b25ee68bc53c07150b12b5eb7028b6182f1c97f71f7e21c8ee1d/_data",
"Destination": "/var/lib/mysql",
...
the Source is the folder on host that docker mounted as Destination in the VM
if you want all volumes removed when deleting a docker container, use
$ docker rm -v container-id
otherwise, the volumes will stay on your disk, taking up space
you can also list all volumes registered by docker:
$ docker volume ls
and use this to remove all "dangling" volumes:
$ docker volume rm $(docker volume ls -qf dangling=true)
let's check if mysql is up and running, through the terminal. you can open an interactive terminal in the image like this:
$ docker exec -it dockertut_mysql_1 /bin/bash
root@0e4f40d9ae8b:/# mysql -u root -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
...
mysql> _
neat, huh? the -i means to run an interactive command, -t to run in a terminal
manage Docker as a non-root user
by creating the docker group and adding users to it, you can select users that can manage the docker daemon and processes:
$ sudo groupadd docker
$ sudo usermod -aG docker $USER
afterwards, restart your machine (or log out and in, and stop and start the docker daemon), and you should be able to control docker without the need for sudo