paskvilwiki

up: servers & admin

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