1. Introduction
Containerization is about packaging an application, libraries and configuration as an image
The image can be shared easily with others publicly or in a limited scope
Instances of the image (the containers) can be quickly spawned in a portable way
Containers are isolated from the host system and from each other (except when explicitly configured otherwise)
- This allows to design a software system as a set of communicating microservices
- This also enhances security, because a compromised component does not grant access to the rest of the system
2. Containerization vs Virtualization
- VMs emulate the whole stack (hardware + software), while containers share some of the resources with the host system
- VMs require more resources to operate and take longer to start
- VMs are usually mutable -- one creates an instance from base image, then installs
X
and configuresY
, then after a few weeks one installsZ
, etc. - Containers are immutable -- if you want to update
X
installed, you rebuild the container image and restart it
3. Container types
- A system container is one which emulates VM behaviour
- It has a main process (
/sbin/init
) able to spawn other daemons and foreground applications - An application container has just one application running as its only process
- The best practice is to design application containers and connect them using well-defined channels
4. Basic commands
These examples use Docker as the container engine
To download
ubuntu
image from the public repository:docker pull ubuntu
To list available images:
docker images
To run a container from
ubuntu
image:docker run -it ubuntu
The
-it
stands for:-i, --interactive Keep STDIN open even if not attached -t, --tty Allocate a pseudo-TTY
It is required to run interactive commands in a shell
To list running containers:
docker ps
5. Specific images
- The public repository called the Docker Hub contains a lot of ready-to-use images
- Some of the images contain a base OS:
- Some contain compilers, interpreters or other useful tools:
- Others contain known servers or DBMS:
- It is always a good idea to get acknowledged with the documentation of each specific image
- Creators of these images follow certain conventions to make the usage of image easier
6. Examples
To run an HTTP server hosting a set of HTML files:
echo '<h1>Hello World from Docker!</h1>' > index.html docker run -p 8080:80 -v "$(pwd):/usr/local/apache2/htdocs" httpd
The
-p
and-v
flags stand for:-p, --publish list Publish a container's port(s) to the host -v, --volume list Bind mount a volume
The
-p 8080:80
allows to bindlocalhost:8080
on the host system, tolocalhost:80
inside the containerThe
-v "$(pwd):/usr/local/apache2/htdocs"
mounts the current directory on the host as/usr/local/apache2/htdocs
inside the container
To check if your Maven project builds correctly in a clean environment:
mvn archetype:generate -DarchetypeArtifactId=maven-archetype-quickstart -DgroupId=pl.psnc -DartifactId=docker-training -Dversion=1.0.0 -DinteractiveMode=false docker run -v "$(pwd)/docker-training:/usr/src/docker-training" -w /usr/src/docker-training maven:3-openjdk-8 mvn package
- The
-v
is used in a similar way as previously - The
-w /usr/src/docker-training
sets the working directory when the container launches - The image is
maven:3-openjdk-8
, to select specific OpenJDK version
- The
To start a PostgreSQL instance with a pre-defined schema and initial data:
$ cat 1-init.sql CREATE TABLE data ( id SERIAL PRIMARY KEY, value INTEGER ); $ cat 2-fill.sql INSERT INTO data (value) VALUES (100), (200), (300), (400), (500); docker run -e POSTGRES_PASSWORD=qwerty -v "$(pwd):/docker-entrypoint-initdb.d" -p 5432:5432 postgres
The
-e POSTGRES_PASSWORD=qwerty
sets value to an environment variableThe
-v
mounts the SQL scripts in a path loaded at runtimeThe
-p
is used to publish the default PostgreSQL port from the container to the hostIn another terminal, you can check if the database works correctly:
By executing a query inside the container:
echo 'SELECT * FROM data' | docker exec -i sql-test psql --user postgres
docker exec
allows to execute a command inside a running container-i
means that the standard input from the host (the SQL query) is passed to the process in the container
By executing a query in the host OS: (requires that you have
psql
command line tool available)echo 'SELECT * FROM data' | psql --host=localhost --username=postgres
7. Applications with multiple containers
Keep the PostgreSQL container running
Prepare the following
index.php
file:<?php $dbconn = pg_connect("host=localhost port=5432 user=postgres password=qwerty"); $result = pg_query($dbconn, "SELECT id, value FROM data"); echo "<table><thead><tr><th>Id</th><th>Value</th></tr></thead><tbody>"; while ($row = pg_fetch_row($result)) { echo "<tr><td>$row[0]</td><td>$row[1]</td></tr>"; } echo "</tbody></table>"; ?>
- The script will connect to the database, select all rows in the
data
table and create an HTML table with the result
- The script will connect to the database, select all rows in the
To run an Apache HTTP server with PHP:
docker run -it -p 8080:80 -v "$(pwd):/var/www/html" php:apache sh -c 'apt-get update; apt-get install -y libpq-dev; docker-php-ext-install pgsql; apache2-foreground'
- Take note that
php:apache
uses different default path thanhttpd
image used - The command to be run in the container will (1) update packages list, (2) install
libpq-dev
, (3) installpgsql
PHP extension and finally (4) run the HTTP server - This is not the optimal way of using the
php:apache
image; it would be better to build a new image on top ofphp:apache
, but this will be covered in the next tutorial
- Take note that
You can try opening http://localhost:8080, however you will only see a connection error
The error appears because the PostgreSQL container is isolated
It publishes its 5432 port to the host system, but not to the other containers
To overcome this, let's create a Docker network:
docker network create docker-training
Restart the PostgreSQL container, but (1) put it into the
docker-training
network and (2) name it explicitlydocker run -e POSTGRES_PASSWORD=qwerty -v "$(pwd):/docker-entrypoint-initdb.d" -p 5432:5432 --network docker-training --name db postgres
Modify the
index.php
@@ -1,5 +1,5 @@ <?php -$dbconn = pg_connect("host=localhost port=5432 user=postgres password=qwerty"); +$dbconn = pg_connect("host=db port=5432 user=postgres password=qwerty"); $result = pg_query($dbconn, "SELECT id, value FROM data");
Restart the PHP container, but also put it into the
docker-training
networkdocker run -it -p 8080:80 -v "$(pwd):/var/www/html" --network docker-training php:apache sh -c 'apt-get update; apt-get install -y libpq-dev; docker-php-ext-install pgsql; apache2-foreground'
Now you can see the result in http://localhost:8080
Note that you can omit
-p 5432:5432
when running PostgreSQL container unless you want to connect to database from the host
8. Docker Compose
Running a multi-container application in the way described so far is tedious and error-prone
To address such issues you can use Docker Compose
- The whole application is described in a declarative YAML file
- The file can be source-controlled in git or SVN and easily shared with others
To recreate the previous application:
Create directories
db
andweb
Copy
1-init.sql
and2-fill.sql
todb/
Copy
index.php
toweb/
Create the following
docker-compose.yml
file:version: "3.9" services: db: image: "postgres" environment: - POSTGRES_PASSWORD=qwerty volumes: - "./db:/docker-entrypoint-initdb.d" web: image: "php:apache" command: "sh -c 'apt-get update; apt-get install -y libpq-dev; docker-php-ext-install pgsql; apache2-foreground'" ports: - "8080:80" volumes: - "./web:/var/www/html"
In this file you described
db
andweb
servicesBy default Docker Compose will create a new Docker network and put both containers inside under these names
Take note that in the
volumes
section you can use relative paths (command line Docker requires absolute paths)You can put into a git repository all files i.e. the
db/
andweb/
directories and the YAML fileTo run the Docker Compose application
docker compose up
Please note, that unlike
docker run
the containers started by Docker Compose are not destroyed but just stopped when you exit the above commandThis has consequences for certain images; for example,
postgres
image does not read/docker-entrypoint-initdb.d/
if it detects that there is a database already; if you start Docker Compose, then stop it to modify the initial SQL, then you need to rundocker compose rm
to enforce removal of containers and restart from scratch the next time
This work has been carried out within the framework of the EUROfusion Consortium, funded by the European Union via the Euratom Research and Training Programme (Grant Agreement No. 101052200—EUROfusion). Views and opinions expressed are however those of the author(s) only and do not necessarily reflect those of the European Union or the European Commission. Neither the European Union nor the European Commission can be held responsible for them. The scientific work is published for the realization of the international project co-financed by Polish Ministry of Science and Higher Education in 2021 from financial resources of the program entitled "PMW” 5218/HEU - EURATOM/2022/2