A Docker & Containers tutorial

Chi Thuc Nguyen
7 min readAug 25, 2020

Installing Docker

Ubuntu

From official docs: https://docs.docker.com/engine/install/ubuntu/

Uninstalling old versions:

sudo apt-get remove docker docker-engine docker.io containerd runc

Set up the respository

sudo apt update

Install packages to allow apt to use a repository over HTTPS:

sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
gnupg-agent \
software-properties-common

Add Docker’s official GPG key:

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

Add Docker repository:

sudo add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable"

Install Docker CE

$ sudo apt update
$ sudo apt-get install docker-ce docker-ce-cli containerd.io

Verify that Docker CE is installed correctly by running the hello-world image:

$ sudo docker run hello-world
$ sudo docker images

Manage Docker as a non-root user

The docker daemon binds to a Unix socket instead of a TCP port. By default that Unix socket is owned by the user root and other users can only access it using sudo. The docker daemon always runs as the root user.

If you don’t want to use sudo when you use the docker command, create a Unix group called docker and add users to it. When the docker daemon starts, it makes the ownership of the Unix socket read/writable by the docker group.

To create the docker group and add your user:

  1. Create the docker group and add your user to the docker group:
sudo groupadd docker
sudo usermod -aG docker $USER

2. Log out and log back in so that your group membership is re-evaluated.

Deploying Your First Docker Container

Running A Container

You can find existing images at https://store.docker.com/ or by using the command docker search <name>. For example, to find an image for Redis, you would use:

docker search redis

The Docker CLI has a command called run which will start a container based on a Docker Image. The structure is docker run <options> <image-name>/.

To run the latest (default) release of Redis image as a background service:

docker run -d redis

If a particular version was required, it could be specified as a tag, for example, version 3.2 would be docker run -d redis:3.2.

Note: without option -d, the container will start then stop immediately after finishing specific command. For example: docker run alpine ls /

Finding Running Containers

Lists all running containers, the image used to start the container and uptime:

docker ps

Sample out:

Get more details about a running container:

docker inspect <friendly-name|container-id>

Display messages the container has written to standard error or standard out:

docker logs <friendly-name|container-id>

Accessing Redis

The reason is that each container is sandboxed. If a service needs to be accessible by a process not running in a container, then the port needs to be exposed via the Host. Ports are bound when containers are started using -p <host-port>:<container-port> option:

docker run -d --name redisHostPort -p 6379:6379 redis:latest

By default, the port on the host is mapped to 0.0.0.0, which means all IP addresses. You can specify a particular IP address when you define the port mapping, for example, -p 127.0.0.1:6379:6379

The option -p 6379 exposes Redis but on a randomly available port. This will allow us to run multiple instances of Redis container at the same time:

docker run -d --name redisDynamic -p 6379 redis:latest

To discover that random port, use this command: docker port redisDynamic 6379 or use familiar docker ps command.

Persisting Data

Containers are designed to be stateless, in the way that that the data stored keeps being removed when you delete and re-create a container. Binding directories (also known as volumes) with option -v <host-dir>:<container-dir> allow data to be persisted on the host. This allows you to upgrade or change containers without losing your data.

docker run -d --name redisMapped \
-v /opt/docker/data/redis:/data redis
docker ps -a

Getting access to a bash shell inside of a container

To exec a command inside the container:

docker exec {container-id} {command}

To interact with the container (for example, to access a bash shell) you could include the options -it:

docker exec -it {container-id} sh

or:

docker exec -it {container-id} /bin/bash

Get help form CLI

docker --help
docker {command} --help

Useful commands

List containers:

docker ps -a

Stop using container id

docker stop <id>

Remove a container

docker rm <id>

List images

docker images

Remove an image

docker image rm <image_name:tag or id>

Building your own Container Images

Create Dockerfile

Docker Images start from a base image. The base image should include the platform dependencies required by your application, for example, having the JVM or CLR installed.

This base image is defined as an instruction in the Dockerfile. Docker Images are built based on the contents of a Dockerfile. The Dockerfile is a list of instructions describing how to deploy your application.

In this example, our base image is the Alpine version of Nginx. This provides the configured web server on the Linux Alpine distribution.

Create your Dockerfile for building your image by copying the contents below into the editor:

FROM nginx:alpine
COPY . /usr/share/nginx/html

You should have an index.html file as well:

<h1>Hello World</h1>

Build Docker Image

The Dockerfile is used by the Docker CLI build command. The build command executes each instruction within the Dockerfile. The result is a built Docker Image that can be launched and run your configured app.

The format is docker build -t <build-directory>. The -t parameter allows you to specify a friendly name for the image and a tag, commonly used as a version number.

docker build -t webserver-image:v1 .

Then you can view a list of all the images on the host using:

docker images

Launch the image

Launch our newly built image providing the friendly name and tag. As it’s a web server, bind port 80 to our host using the -p parameter.

docker run -d -p 80:80 --name webapp webserver-image:v1

Once started, you’ll be able to access the results of port 80 via curl docker

Container Orchestration using Docker Compose

When working with multiple containers, it can be difficult to manage the starting along with the configuration of variables and links. To solve this problem, Docker has a tool called Docker Compose to manage the orchestration, of launching, of containers.

Installing Docker Compose

Official docs: https://docs.docker.com/compose/install/

To install Docker Compose, just download latest version (found on the link above) and apply executable permission to the binary:

sudo curl -L "https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-composesudo chmod +x /usr/local/bin/docker-composedocker-compose --version

Official tutorial

Another tutorial:

Docker compose file

Docker Compose files work by applying mutiple commands that are declared within a single docker-compose.yml configuration file. The basic structure of a Docker Compose YAML file looks like this:

version: 'X'services:
web:
build: .
ports:
- "5000:5000"
volumes:
- .:/code
redis:
image: redis

Example:

version: '3'
services:
web:
# Path to Dockerfile.
# '.' represents the current directory in which
# docker-compose.yml is present.
build: .
# Mapping of container port to host

ports:
- "5000:5000"
# Mount volume
volumes:
- "/usercode/:/code"
# Link database container to app container for reachability.
links:
- "database:backenddb"

database:
# image to fetch from docker hub
image: mysql/mysql-server:5.7
# Environment variables for startup script
# container will use these variables
# to start the container with these define variables.
environment:
- "MYSQL_ROOT_PASSWORD=root"
- "MYSQL_USER=testuser"
- "MYSQL_PASSWORD=admin123"
- "MYSQL_DATABASE=backend"
# Mount init.sql file to automatically run
# and create tables for us.
# everything in docker-entrypoint-initdb.d folder
# is executed as soon as container is up nd running.
volumes:
- "/usercode/db/init.sql:/docker-entrypoint-initdb.d/init.sql"

Useful commands

These commands should be run inside a folder with docker-compose.yml.

Builds images in the docker-compose.yml file:

docker-compose build

Create and start containers:

docker-compose up

Start and run containers in background (detach mode):

docker-compose up -d

Stops the running containers of specified services:

docker-compose stop

List all the containers in the current docker-compose file:

docker-compose ps

Remove containers, networks and images (for services defined in the compose file):

docker-compose down

Scale with multiple instances of a service:

docker-compose up -d --scale SERVICE=NUM

for example:

docker-compose up -d --scale workers=5

--

--