- Published on
Docker 101: Quick Start Guide
This article is a synthesis of the basics to get started with Docker. No long theory, just the essentials to get started quickly.
After reading this article, you will be able to:
- 📚 understand Docker’s main concepts and the indispensable vocabulary
- 🚢 use a Docker image and manage its lifecycle
- ✍🏻 write your own tailored image and optimize it with multistage builds
(this is a realy simple article to understand the basics)
📚 Concepts
What is Docker?
Docker is an application released in 2013 to manage containers. Others exist, but to date it’s probably the best known and most used. Containerization is a radical evolution of what virtual machines enable.
Virtual machines, just like Docker, allow an application to run in a controlled and isolated environment on your computer’s or a server’s operating system (called the host).
Reminder about virtual machine (VM) architecture
To make it clear, a quick reminder about the principle of virtual machines. They allow you to install a full operating system on your machine, and inside that OS you embed your application.
Example: on your Windows machine, you launch a Debian server that embeds a web server.
The main benefit is that your application (the web server*) will run in a totally known and controlled environment (Debian*). The virtual machine itself is managed by a hypervisor that bridges to your machine (Windows*).
(*) refer to the example above
Docker architecture
By contrast, Docker is an application on your machine that spawns containers. These containers embed only the libraries and tools required for your application to run and do not need their own OS because they have direct access to your machine’s (host) resources.
Comparing the architecture diagrams, it’s immediately clear that containers are therefore:
- much lighter in disk space because they only ship the minimum required
- much faster to start or stop because there is no OS
- much closer to your host machine’s resources and therefore more responsive
Nevertheless, direct access to your machine’s or a server’s resources requires that it runs on Linux or a compatible OS.
💥 What to remember 💥
From images, Docker creates containers and manages their lifecycle within a private space. These images can be retrieved from a registry or created by you to meet your specific needs.
And the following important keywords:
- Host: the machine that hosts Docker and shares its resources with containers. It’s your computer or, for example, a server.
- Image: an image is a file that describes the libraries and tools required by your application. It is usually authored in a file named Dockerfile.
- Container: it’s an image that has been built, meaning all the necessary bits have been installed.
- Lifecycle: once built, a container may be stopped, running, or in a transitional state (starting, restarting, …) or unstable (crash).
- Registry: a server hosting and providing access to ready‑to‑use Docker images. It can be public or private.
🚢 Essential commands
Now that we have the theory, let’s get hands‑on. To begin, you need to install Docker. Installation depends on your OS: https://docs.docker.com/get-docker/
For examples we’ll use the hello-world Docker image, but you can try directly with others.
Start a Docker container
This is the command whose options are the most useful to understand. In all the examples below, I use the hello-world image as the example.
docker run -it hello-world
# Start the container and give it a name so it’s easy to find later
docker run -it --name myimage hello-world
# Start the container and keep it running in the background (daemon)
docker run -it -d hello-world
Quickly, you’ll use containers exposing ports to communicate with the application. Other containers allow configuring applications via environment variables.
# Start a container that exposes port 3000 and make it reachable on your machine on port 8080
docker run -p8080:3000 hello-world
# Start a container while sharing a local volume
docker run -v $(pwd):/backup hello-world
# Start a container while passing it an environment variable
docker run -e myCustomEnvVarName=someValueINeed hello-world
To know which ports, volumes, and environment variables are available, refer case‑by‑case to the explanations provided with each image (or read the image files directly if the docs are missing…).
Many other options exist, notably for mounted volumes (permissions and privileges, for example). Refer to the documentation when needed :)
Manage your containers
Once started, your Docker containers can run in the background (-d daemon) and you’ll need to list, stop, remove, restart them… Below are the most common basic commands:
# list existing containers
docker ps -a
# stop a container
docker stop CONTAINER_ID
# restart a stopped container
docker start CONTAINER_ID
# remove a container
docker rm CONTAINER_ID
Clean up Docker instances
Docker can quickly consume a lot of disk space and it’s good to know how to clean up :)
# Clean images, containers, volumes, and networks that are dangling (not associated with a container)
docker system prune
# Also remove all stopped containers and all unused images
docker system prune -a
# remove unused images
docker image prune -a
# remove exited containers
docker rm $(docker ps -a -f status=exited -q)
Go further
Many other commands exist: to manage networks (to make your containers communicate with your host or other containers), to collect metrics, or even to enhance the commands shown (filter results, search containers based on images, …). Refer to the documentation!
✍🏻 Create your tailored Docker image
Although registries can offer a large number of images, they remain very generic. When they are well designed, using volumes, ports, or configuring them via environment variables provides a first layer of flexibility. To go further you can, starting from each of them, add build phases specific to your project.
Let’s take as an example a Node API server. To run it will require:
- An environment supporting Node 10
- The source code of our application (in the same directory as the Dockerfile)
- Installing its dependencies (node_modules)
- A build phase
A basic image
# Base docker image: alpine with node. Others exist; pick the one that suits you best
FROM mhart/alpine-node:12.13.1
# Create a working directory on the image where we’ll put the application source code
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
# Install dependencies in our working directory
COPY package.json yarn.lock ./
RUN set -ex; \
yarn install --no-cache --frozen-lockfile --production;
# Copy the app sources (here, we copy everything; adjust as needed because often only a few directories are actually useful (src, assets, public...))
COPY . .
# Build the app with the npm script `production-build`; replace with yours
RUN set -ex; \
yarn run production-build;
# Default startup, which can be different when using your image
EXPOSE 3000
CMD ["yarn", "start"]
An image using env vars for a conditional build
Practical example that uses the NODE_ENV environment variable to decide whether to install development npm dependencies or not.
FROM mhart/alpine-node:12.13.1
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
COPY package.json yarn.lock ./
ARG NODE_ENV
ENV NODE_ENV $NODE_ENV
RUN set -ex; \
if [ "$NODE_ENV" = "production" ]; then \
yarn install --no-cache --frozen-lockfile --production; \
else \
yarn install --no-cache --development; \
fi;
COPY . .
RUN set -ex; \
if [ "$NODE_ENV" = "development" ]; then \
echo "Skip production build"; \
else \
echo "Production build"; \
yarn run production-build; \
fi;
EXPOSE 3000
CMD ["yarn", "start"]
An optimized image (multistage)
Example with multiple stages. The image is built in several steps with intermediate builds. If nothing changed during a step, Docker will reuse the cached intermediate build, avoiding for example re-downloading all npm packages.
# base
FROM mhart/alpine-node:12.13.1 AS base
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
COPY package.json yarn.lock ./
# dependancies
FROM base AS dependancies
ARG NODE_ENV
ENV NODE_ENV $NODE_ENV
RUN set -ex; \
if [ "$NODE_ENV" = "production" ]; then \
yarn install --no-cache --frozen-lockfile --production; \
else \
yarn install --no-cache --development; \
fi;
# build only in production
FROM dependancies AS build
COPY . .
RUN set -ex; \
if [ "$NODE_ENV" = "development" ]; then \
echo "Skip production build"; \
else \
echo "Production build"; \
yarn run production-build; \
fi;
EXPOSE 3000
CMD ["yarn", "start"]
Go further
At this point, you should be able to use a Docker image without too much trouble.
Quickly, you will want or need to run multiple applications together: a frontend and an API, an API and a database, … From there, head to docker-compose, an orchestrator that, with a simple configuration file, lets you create complete stacks on a server.
If a Docker image can provide a simple service on a single server, you will probably need to make it scalable to handle more load (connections, load balancing, server resources, etc.). You can then dive into swarm mode or Kubernetes to run your containers in a cluster (among other things).
Note: This is a literal translation of my original article on Medium: https://medium.com/@devpulsion/docker-101-guide-de-démarrage-rapide-48e99d80946a