You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 2 Current »

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 configures Y, then after a few weeks one installs Z, 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 bind localhost:8080 on the host system, to localhost:80 inside the container

    • The -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
  • 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 variable

    • The -v mounts the SQL scripts in a path loaded at runtime

    • The -p is used to publish the default PostgreSQL port from the container to the host

    • In 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
  • 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 than httpd image used
    • The command to be run in the container will (1) update packages list, (2) install libpq-dev, (3) install pgsql 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 of php:apache, but this will be covered in the next tutorial
  • 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 explicitly

    docker 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 network

    docker 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 and web

    • Copy 1-init.sql and 2-fill.sql to db/

    • Copy index.php to web/

    • 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 and web services

  • By 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/ and web/ directories and the YAML file

  • To 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 command

  • This 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 run docker compose rm to enforce removal of containers and restart from scratch the next time

  • No labels