Docker Explained: Dockerfile & Compose Examples

Canberk
10 min readJul 3, 2023

--

Before we start with Kubernetes and advanced documentation and guides, I would like to start by examining Docker and its components, the base technology underlying Kubernetes. Let’s start by talking a little bit about Docker.

What is Docker ?

Docker is a computer program that provides virtualization at the operating system level, also known as “containerization”. The first version was released in 2013. Docker is used to run software packages called “containers”. Containers can run isolated and independent of each other. All containers run on a single operating system kernel, making them lighter than virtual machines. Containers are built from read-only file systems called “images”. What Docker really does is decouple application code from infrastructure requirements. It does this by running each application in an isolated environment called a “container”. Docker is a software solution for building comprehensive applications.

You can install your local environments with reading my past article “Widely Used Container and Kubernetes Distributions for Local Environments“.

After the advertisement 🙂 Let’s talk about the basic components of Docker and let’s realize real-life examples where we talk about these components.

Docker Architecture

Docker uses a client-server architecture. The docker client talks to the docker daemon to help build, execute, and distribute docker containers. The Docker client runs on the same system as the daemon or we can remotely connect the Docker client to the Docker daemon. Docker clients and daemons communicate with each other with the help of REST APIs over UNIX sockets or networks.

Docker Components

Images: Docker images are the building blocks for containers. They are an immutable or read-only template that provides guidelines for a Docker container. They contain libraries, dependencies, source code, and other files needed for an application to run. Docker images are built in YAML and are used to store and ship applications.

Containers: A Docker container is a lightweight, self-contained executable package of an application. It contains everything needed to run the application: source code, libraries, dependencies, runtime, system tools and settings. Containers are created using Docker images using the docker run command. Because containers are lightweight in nature, they can be built in seconds with low resource overhead.

Registry: A Docker registry is a repository on which various container images are hosted. Docker images are built in the YAML language and come in various formats. The registry is organized into repositories where each repository stores all versions of a particular image. Docker Hub is Docker’s public registry. You can also create your own registry in your own environment with solutions like Harbor or Nexus.

Volumes: Persistent data created by docker and used by Docker containers is stored in Volumes. They are managed entirely through the docker CLI or Docker API. Fragments work in both Windows and Linux containers. Using blocks for that purpose instead of keeping the data writable in the object is always a better approach. The contents of the volume are available after the life of the container, so using the volume does not increase the size of the object.

Networks: Docker networking is a way for all isolated containers to communicate. There are five main network drivers in docker:

Bridge: This is the default network driver for the container. You use this network when your application is running on independent containers, ie. multiple containers communicating with a single docker host.
Host: This driver removes the network isolation between docker containers and the docker host. This is used when you do not need any network isolation between hosts and containers.
Overlay: This network enables swarm services to communicate with each other. Used when containers are running on Docker hosts or when multiple applications run swarm services.
None: This driver disables all networking.
macvlan: This driver assigns mac addresses to containers so that they look like physical machines. Traffic between containers is routed by their MAC address. This grid is used when you want containers to look like physical machines, for example, running a VM system.

Examples run a container with Docker CLI

To run Docker containers, you can easily stand up containers with parameters from the images you want by giving parameters directly from cli. Docker commands using the nginx, php, and postgres images:

# Download the latest nginx image from Docker Hub
docker pull nginx
# If you don't use pull command the under run command will execute that command with image and versions before running.

# Run the nginx container
# -d stands for detached mode, which means the container runs in the background
# -p maps the host's port 80 to the container's port 80
# -- name <container's name> <image-id:image-version>
docker run -d -p 80:80 --name nginx-container nginx:latest
# Pull the wordpress image from Docker Hub
docker pull wordpress
# Run the wordpress container
# -p sets expose ports your <host's port>:<container's port>
# -e sets environment variables used by the wordpress image to set up the website
# -- name <container's name> <image-id:image-version>
docker run -d -p 8080:80 -e WORDPRESS_DB_HOST=db -e WORDPRESS_DB_USER=wp_user -e WORDPRESS_DB_PASSWORD=wp_password -e WORDPRESS_DB_NAME=wp_database --name wordpress-container wordpress:6.0.2
# Pull the postgres image from Docker Hub
docker pull postgres
# Run the postgres container
# -e sets environment variables used by the postgres image to set up the database
# -p sets expose ports your <host's port>:<container's port>
# -- name <container's name> <image-id:image-version>
docker run -d -p 5432:5432 -e POSTGRES_PASSWORD=mysecretpassword --name postgres-container postgres:13

What is Dockerfile ?

Dockerfile is the configuration file used for the creation of Docker containers. The Dockerfile specifies how to run the application and which dependencies to install. Docker creates a container using this Dockerfile and converts this container into a file called an image. This image can then be run on any system.

Dockerfile is a script file that defines the file system. Dockerfile consists of line-by-line configuration commands, and each command performs an operation, such as creating a file or directory, modifying the contents of a file or directory. The Dockerfile specifies how the application will be run and which dependencies to install, so that everything needed for the application to run is taken into account when creating the Docker container. The Dockerfile also specifies the processes running inside the container and how to manage them.

Dockerfile Examples

Here are Dockerfile examples for nginx, a PHP application, and postgres.

Example NGINX’s Dockerfile:

# Use the nginx image as the base
FROM nginx
# Copy custom configuration file from local system into the Docker image
# Here, ./nginx.conf is a file on the local system, and /etc/nginx/nginx.conf is its destination in the image
COPY ./nginx.conf /etc/nginx/nginx.conf

Example PHP Dockerfile:

# Use the PHP 7.3 Apache image as the base
FROM php:7.3-apache
# Copy the PHP application from the local system into the Docker image
COPY . /var/www/html
# Use Docker's multi-stage build feature to only include necessary components in the final image
# Here, "composer install" is used to install the application's dependencies
FROM composer:1.9 as vendor
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-scripts --no-autoloader
# Copy installed dependencies from the vendor image
COPY --from=vendor /app/vendor /var/www/html/vendor
# Expose port 80
EXPOSE 80

Example Postgres Dockerfile:

# Use the Postgres image as the base PostgreSQL 15 version
FROM postgres:15
# Copy custom SQL scripts from the local system into the Docker image
COPY ./scripts/ /docker-entrypoint-initdb.d/

If you want to look more details with Dockerfiles and all parameters, you can check official Docker guide about Dockerfiles.

End-to-end Containarize a PHP Web App

Now, let’s containarize a sample php 7.4 application using Dockerfile. I will explain the example assuming you are in the directory where your php 7.4 application is located.

First, let’s build our Dockerfile:

# Use the official PHP 7.4 image as a base. This image includes PHP 7.4 and a minimal Debian-based system.
FROM php:7.4
# Set the working directory in the Docker image filesystem.
# Subsequent commands that run will start from this directory.
WORKDIR /var/www/html
# Copy the current directory contents (on your host) into the container.
# It's assuming that your PHP application's codebase exists in the same directory as your Dockerfile.
COPY . .
# Update the package lists for upgrades and new package installations.
# Then, install the necessary packages:
# - libzip-dev and zip are required to use the ZIP PHP extension
# - libpq-dev is necessary for the PDO PostgreSQL extension
# - default-mysql-client and default-libmysqlclient-dev are needed for MySQL
# - docker-php-ext-install is a script included with the PHP Docker image that you can use to more easily install PHP extensions
# - apt-get clean and rm -rf /var/lib/apt/lists/* are used to clean up unnecessary files and free up space.
# This is done because Docker images are constructed as layers, and if the cleanup was done in a separate RUN command,
# the files would still exist in a lower layer.
RUN apt-get update && \
apt-get install -y --no-install-recommends \
libzip-dev \
zip \
libpq-dev \
default-mysql-client \
default-libmysqlclient-dev \
&& \
docker-php-ext-install zip pdo pdo_mysql pdo_pgsql && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# Inform Docker that the container listens on the specified network port at runtime.
# This does not actually publish the port. It functions as a type of documentation between
# the person who builds the image and the person who runs the container.
EXPOSE 80
# Set environment variables for database parameters.
# These environment variables can be accessed from your PHP code using the $_ENV superglobal, getenv function, or the server superglobal.
# The variables would typically be replaced with actual values for a database.
# Hardcoding credentials in a Dockerfile is not recommended for security reasons.
ENV DB_HOST your-db-host
ENV DB_NAME your-db-name
ENV DB_USERNAME your-db-username
ENV DB_PASSWORD your-db-password
# The main purpose of a CMD is to provide defaults for an executing container.
# This default can include an executable, or they can omit the executable,
# in which case you must specify an ENTRYPOINT instruction.
# The CMD instruction only gets executed when there is no command line arguments provided while executing the docker run command.
# In this case, we're starting the PHP built-in server.
CMD ["php", "-S", "0.0.0.0:80"]

This Dockerfile performs several actions:

  • Base image selection: Starting from the official php:7.4 base image, including PHP 7.4 on a minimum Debian-based system. This does not include Apache, but you use the built-in PHP server instead.
  • Working Directory Configuration: This sets the working directory in the Docker image to /var/www/html. All subsequent commands will be issued from this directory.
    Copy the application: This copies the contents of the current directory (which could be your PHP application) to the /var/www/html directory in the image. This will be the location of your application files in the Docker container.
  • Package installation: Refreshes the package list and installs packages and PHP extensions required for the application. It contains packages and extensions for handling ZIP files and connecting to MySQL and PostgreSQL databases. Clean up unnecessary files from the package manager to minimize image size after installation.
  • Port Exposure: Exposes 80 ports to allow incoming connections. This is the default port that the PHP built-in server will use.
  • Environment Variable Configuration: This configures the environment variables that your PHP application can use to connect to the database. This allows you to be flexible and secure as you can edit these variables in any case without changing the code.
  • Command Definition: This sets the default commands that Docker will execute when the container is started from this image. In this case, it starts the PHP built-in server, so that your PHP application is immediately available on port 80 of the container.

Now, with reference to our Dockerfile, let’s build a container, run it and push it to the registry so that it can be used in different environments or run by our teammates or publicly. Inside the directory where the Dockerfile is located. Build our new image with ; myregistryusername/my-php-app:1.0 tag.

docker build -t myregistryusername/my-php-app:1.0 .

This command does the following:

  • docker build is the command to build a Docker image from a Dockerfile
    t myregistryusername/my-php-app:1.0 assigns a tag to the image, in the form username/repository:tag. Replace myregistryusername with your Docker Hub username.
  • . symbol indicates that the Dockerfile is in the current directory.

Next, run a container using the new image:

docker run -d -p 8080:80 --name my-php-app myregistryusername/my-php-app:1.0

Once you have tested that your image works successfully, you are ready to push it to the registry. Before pushing, make sure you are logged in with your Dockerhub account.

docker push myregistryusername/my-php-app:1.0

Yes ! we have containerized an end-to-end php application. With this method, you can use any application you want (wordpress, js apps, ruby apps, python apps etc.) by dockerizing it with a base image.

Docker Compose

Compose is a tool for defining and running multi-container docker applications. With Compose, we can configure application services using a YAML file. Then, with a command, we can initialize our configurations by creating all services.

With Docker compose, we can ensure that the images we create or ready-made images run with the parameters and configurations we want. By setting different volumes, networks, environments and other parameters in a single .yaml, we can stand up one or more containers with a single file and manage them with docker-compose commands.

Examples Docker-Compose.yaml

Example Nginx docker-compose.yaml:

version: '3'
services:
nginx:
image: nginx
ports:
- "80:80"
volumes:
# Mount custom configuration file
- ./nginx.conf:/etc/nginx/nginx.conf
restart: always

Example WordPress stack docker-compose.yaml:

version: '3.3'
services:
db:
image: mysql:5.7
volumes:
- db_data:/var/lib/mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: somewordpress
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpress
wordpress:
depends_on:
- db
image: wordpress:latest
ports:
- "8080:80"
restart: always
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
WORDPRESS_DB_NAME: wordpress
volumes:
- wordpress_data:/var/www/html
volumes:
db_data: {}
wordpress_data: {}

As with dockerfile and Docker CLI, there are many parameters and configurations you can use with docker-compose. You can take a look at the Docker Compose guide published by Docker to take a look at them.

We have come to the end of our article! You can use the comments for your questions and problems. If you want more articles like this, don’t forget to share and interact! See you in the next articles,

--

--

Canberk
Canberk

Written by Canberk

Lead Engineer • Tech Trainer • Go Dev • TR/EN github.com/syswe

No responses yet