Quick Links

Docker creates packaged applications called containers. Each container provides an isolated environment similar to a virtual machine (VM). Unlike VMs, Docker containers don't run a full operating system. They share your host's kernel and virtualize at a software level.

Docker Basics

Docker has become a standard tool for software developers and system administrators. It's a neat way to quickly launch applications without impacting the rest of your system. You can spin up a new service with a single docker run command.

Containers encapsulate everything needed to run an application, from OS package dependencies to your own source code. You define a container's creation steps as instructions in a

        Dockerfile
    

. Docker uses the Dockerfile to construct an image.

Images define the software available in containers. This is loosely equivalent to starting a VM with an operating system ISO. If you create an image, any Docker user will be able to launch your app with docker run.

How Does Docker Work?

Containers utilize operating system kernel features to provide partially virtualized environments. It's possible to create containers from scratch with commands like

        chroot
    

. This starts a process with a specified root directory instead of the system root. But using kernel features directly is fiddly, insecure, and error-prone.

Docker is a complete solution for the production, distribution, and use of containers. Modern Docker releases are comprised of several independent components. First, there's the Docker CLI, which is what you interact with in your terminal. The CLI sends commands to a Docker daemon. This can run locally or on a remote host. The daemon is responsible for managing containers and the images they're created from.

The final component is called the container runtime. The runtime invokes kernel features to actually launch containers. Docker is compatible with runtimes that adhere to the OCI specification. This open standard allows for interoperability between different containerization tools.

You don't need to worry too much about Docker's inner workings when you're first getting started. Installing docker on your system will give you everything you need to build and run containers.

Why Do So Many People Use Docker?

Containers have become so popular because they solve many common challenges in software development. The ability to containerize once and run everywhere reduces the gap between your development environment and your production servers.

Using containers gives you confidence that every environment is identical. If you have a new team member, they only need to docker run to set up their own development instance. When you launch your service, you can use your Docker image to deploy to production. The live environment will exactly match your local instance, avoiding "it works on my machine" scenarios.

Docker is more convenient than a full-blown virtual machine. VMs are general-purpose tools designed to support every possible workload. By contrast, containers are lightweight, self-sufficient, and better suited to throwaway use cases. As Docker shares the host's kernel, containers have a negligible impact on system performance. Container launch time is almost instantaneous, as you're only starting processes, not an entire operating system.

Getting Started

Docker is available on all popular Linux distributions. It also runs on Windows and macOS. Follow the Docker setup instructions for your platform to get it up and running.

You can check that your installation is working by starting a simple container:

docker run hello-world

This will start a new container with the basic hello-world image. The image emits some output explaining how to use Docker. The container then exits, dropping you back to your terminal.

Screenshot of Docker "hello world" image output

Creating Images

Once you've run hello-world, you're ready to create your own Docker images. A Dockerfile describes how to run your service by installing required software and copying in files. Here's a simple example using the Apache web server:

FROM httpd:latest

RUN echo "LoadModule headers_module modules/mod_headers.so" >> /usr/local/apache2/conf/httpd.conf

COPY .htaccess /var/www/html/.htaccess

COPY index.html /var/www/html/index.html

COPY css/ /var/www/html/css

The FROM line defines the base image. In this case, we're starting from the official Apache image. Docker applies the remaining instructions in your Dockerfile on top of the base image.

The RUN stage runs a command within the container. This can be any command available in the container's environment. We're enabling the headers Apache module, which could be used by the .htaccess file to set up routing rules.

The final lines copy the HTML and CSS files in your working directory into the container image. Your image now contains everything you need to run your website.

Now, you can build the image:

docker build -t my-website:v1 .

Docker will use your Dockerfile to construct the image. You'll see output in your terminal as Docker runs each of your instructions.

Screenshot of Docker Build output

The -t in the command tags your image with a given name (my-website:v1). This makes it easier to refer to in the future. Tags have two components, separated by a colon. The first part sets the image name, while the second usually denotes its version. If you omit the colon, Docker will default to using latest as the tag version.

The . at the end of the command tells Docker to use the Dockerfile in your local working directory. This also sets the build context, allowing you to use files and folders in your working directory with COPY instructions in your Dockerfile.

Once you've created your image, you can start a container using docker run:

docker run -d -p 8080:80 my-website:v1

We're using a few extra flags with docker run here. The -d flag makes the Docker CLI detach from the container, allowing it to run in the background. A port mapping is defined with -p, so port 8080 on your host maps to port 80 in the container. You should see your web page if you visit localhost:8080 in your browser.

Docker images are formed from layers. Each instruction in your Dockerfile creates a new layer. You can use advanced building features to reference multiple base images, discarding intermediary layers from earlier images.

Image Registries

Once you have an image, you can push it to a registry. Registries provide centralized storage so that you can share containers with others. The default registry is Docker Hub.

When you run a command that references an image, Docker first checks whether it's available locally. If it isn't, it will try to pull it from Docker Hub. You can manually pull images with the docker pull command:

docker pull httpd:latest

If you want to publish an image, create a Docker Hub account. Run docker login and enter your username and password.

Next, tag your image using your Docker Hub username:

docker tag my-image:latest docker-hub-username/my-image:latest

Now, you can push your image:

docker push docker-hub-username/my-image:latest

Other users will be able to pull your image and start containers with it.

You can run your own registry if you need private image storage. Several third-party services also offer Docker registries as alternatives to Docker Hub.

Managing Your Containers

The Docker CLI has several commands to let you manage your running containers. Here are some of the most useful ones to know:

Listing Containers

docker ps shows you all your running containers. Adding the -a flag will show stopped containers, too.

Screenshot of listing Docker containers

Stopping and Starting Containers

To stop a container, run docker stop my-container. Replace my-container with the container's name or ID. You can get this information from the ps command. A stopped container is restarted with docker start my-container.

Containers usually run for as long as their main process stays alive. Restart policies control what happens when a container stops or your host restarts. Pass --restart always to docker run to make a container restart immediately after it stops.

Getting a Shell

You can run a command in a container using docker exec my-container my-command. This is useful when you want to manually invoke an executable that's separate to the container's main process.

Add the -it flag if you need interactive access. This lets you drop into a shell by running docker exec -it my-container sh.

Monitoring Logs

Docker automatically collects output emitted to a container's standard input and output streams. The docker logs my-container command will show a container's logs inside your terminal. The --follow flag sets up a continuous stream so that you can view logs in real time.

Screenshot of Docker logs from an Apache web server container

Cleaning Up Resources

Old containers and images can quickly pile up on your system. Use docker rm my-container to delete a container by its ID or name.

The command for images is docker rmi my-image:latest. Pass the image's ID or full tag name. If you specify a tag, the image won't be deleted until it has no more tags assigned. Otherwise, the given tag will be removed but the image's other tags will remain usable.

Screenshot of docker system prune command

Bulk clean-ups are possible using the docker prune command. This gives you an easy way to remove all stopped containers and redundant images.

Graphical Management

If the terminal's not your thing, you can use third-party tools to set up a graphical interface for Docker. Web dashboards let you quickly monitor and manage your installation. They also help you take remote control of your containers.

Illustration of Portainer on a laptop

Persistent Data Storage

Docker containers are ephemeral by default. Changes made to a container's filesystem won't persist after the container stops. It's not safe to run any form of file storage system in a container started with a basic docker run command.

There are a few different approaches to managing persistent data. The most common is to use a Docker Volume. Volumes are storage units that are mounted into container filesystems. Any data in a volume will remain intact after its linked container stops, letting you connect another container in the future.

Maintaining Security

Dockerized workloads can be more secure than their bare metal counterparts, as Docker provides some separation between the operating system and your services. Nonetheless, Docker is a potential security issue, as it normally runs as root and could be exploited to run malicious software.

If you're only running Docker as a development tool, the default installation is generally safe to use. Production servers and machines with a network-exposed daemon socket should be hardened before you go live.

Screenshot of a Docker Bench for Security report

Audit your Docker installation to identify potential security issues. There are automated tools available that can help you find weaknesses and suggest resolutions. You can also scan individual container images for issues that could be exploited from within.

Working with Multiple Containers

The docker command only works with one container at a time. You'll often want to use containers in aggregate. Docker Compose is a tool that lets you define your containers declaratively in a YAML file. You can start them all up with a single command.

This is helpful when your project depends on other services, such as a web backend that relies on a database server. You can define both containers in your docker-compose.yml and benefit from streamlined management with automatic networking.

Here's a simple docker-compose.yml file:

version: "3"

services:

app:

image: app-server:latest

ports:

- 8000:80

database:

image: database-server:latest

volumes:

- database-data:/data

volumes:

database-data:

This defines two containers (app and database). A volume is created for the database. This gets mounted to /data in the container. The app server's port 80 is exposed as 8000 on the host. Run docker-compose up -d to spin up both services, including the network and volume.

The use of Docker Compose lets you write reusable container definitions that you can share with others. You could commit a docker-compose.yml into your version control instead of having developers memorize docker run commands.

There are other approaches to running multiple containers, too. Docker App is an emerging solution that provides another level of abstraction. Elsewhere in the ecosystem, Podman is a Docker alternative that lets you create "pods" of containers within your terminal.

Container Orchestration

Docker isn't normally run as-is in production. It's now more common to use an orchestration platform such as Kubernetes or Docker Swarm mode. These tools are designed to handle multiple container replicas, which improves scalability and reliability.

Illustration showing the Docker and Kubernetes logos

Docker is only one component in the broader containerization movement. Orchestrators utilize the same container runtime technologies to provide an environment that's a better fit for production. Using multiple container instances allows for rolling updates as well as distribution across machines, making your deployment more resilient to change and outage. The regular docker CLI targets one host and works with individual containers.

A Powerful Platform for Containers

Docker gives you everything you need to work with containers. It has become a key tool for software development and system administration. The principal benefits are increased isolation and portability for individual services.

Getting acquainted with Docker requires an understanding of the basic container and image concepts. You can apply these to create your specialized images and environments that containerize your workloads.