Skip to content
JackSparrow414
Go back

Notes from Reading the Docker Documentation

Table of contents

Open Table of contents

Article body

Docker’s documentation is excellent: its examples are complete and the reasoning flows naturally. Following the sequence gives you a broad understanding of Docker.

This article records notes from Docker’s Get started documentation.

Writing a Dockerfile

# FROM is followed by the image name.
From node:12-alpine
# RUN is an image-build step; here it installs these packages after the base image is pulled.
RUN apk add --no-cache python2 g++ make
# Working directory for subsequent commands
WORKDIR /app
# Copy app from the host's current directory to /app in the container.
COPY ./app .
# Use yarn in the current image.
RUN yarn install --production
# CMD defines the default command run when a container starts after the image is built. Only one CMD takes effect; if there are several, the last wins.
CMD ["node", "src/index.js"]
# Expose the port without mapping it to the host; use -p for that mapping.
EXPOSE 3000

Common Dockerfile Questions

How Do RUN and CMD Differ?

A Stack Overflow answer.

The deeper reason for this question may be an unclear understanding of images and containers.

Docker documentation explaining containers as independently running processes

Once these two concepts are clear, the distinction makes sense.

Building the Application from a Dockerfile

docker build -t getting-started .

The final dot represents the build context. Search for it in the docker build documentation, or search Google for the term if it remains unclear.

The build process appears in the terminal in real time: Terminal output showing image build steps from a Dockerfile

You can see all the steps defined above.

Starting the Application

docker run -dp 3000:3000 getting-started

Persisting Container Data on the Host with a Named Volume

Docker containers are isolated (isolation). Even two containers from the same image cannot share their individual data directly. Changes to container files also disappear when the container is deleted, so they do not persist on their own.

With the previous experiment, we saw that each container starts from the image definition each time it starts. While containers can create, update, and delete files, those changes are lost when the container is removed and all changes are isolated to that container. With volumes, we can change all of this.

Volumes provide the ability to connect specific filesystem paths of the container back to the host machine. If a directory in the container is mounted, changes in that directory are also seen on the host machine. If we mount that same directory across container restarts, we’d see the same files

According to the documentation, volumes store changes from the container on the host, addressing this problem.

Creating a Volume

docker volume create todo-db

Using -v When Starting the Container

docker run -dp 3000:3000 -v todo-db:/etc/todos getting-started

The path above, /etc/todos, is inside the container. The volume tracks files there and stores changes on the host disk, addressing persistence.

Start the todo app container, but add the -v flag to specify a volume mount. We will use the named volume and mount it to /etc/todos, which will capture all files created at the path

Where Is the Volume Data Stored on the Host?

docker volume inspect todo-db

Documentation.

Mounting Host-Managed Data and Configuration into Containers with Bind Mounts

One scenario is local development: source changes can update the running container without rebuilding it.

In the previous chapter, we talked about and used a named volume to persist the data in our database. Named volumes are great if we simply want to store data, as we don’t have to worry about where the data is stored.

With bind mounts, we control the exact mountpoint on the host. We can use this to persist data, but it’s often used to provide additional data into containers. When working on an application, we can use a bind mount to mount our source code into the container to let it see code changes, respond, and let us see the changes right away.

Documentation and comparison of the two approaches.

How Can Named Volumes and Bind Mounts Be Distinguished?

Both are defined under volumes. How do you tell them apart?

todo-db:/etc/todos

/usr/local:/usr/local

Docker also provides —mount, whose type distinguishes volume from bind mounts. The documentation recommends —mount rather than -v.

For detailed data-management guidance and use cases for bind mounts and volumes, see Manage data in Docker.

Combining Containers into a Complete Application with Compose

In general, each container should do one thing and do it well

A complete business application may include a frontend, backend, database, Redis, and a message queue.

In Docker, each belongs in a different container, communicating over a shared network.

Even so, deployment remains somewhat complicated: each container must be deployed and verified separately, then the network configured. Is there a way to deploy them together? This is where Docker Compose comes in.

The big advantage of using Compose is you can define your application stack in a file, keep it at the root of your project repo (it’s now version controlled), and easily enable someone else to contribute to your project. Someone would only need to clone your repo and start the compose app. In fact, you might see quite a few projects on GitHub/GitLab doing exactly this now.

Combining Applications with docker-compose.yml

# Compose schema version, similar to an XML version
version: "3.7"

services:
  app:
    image: node:12-alpine
    # Start a shell with sh (Alpine has no bash), run yarn install to install dependencies, then yarn run dev. In package.json, the dev script starts nodemon.
    command: sh -c "yarn install && yarn run dev"
    # Port mapping
    ports:
      - 3000:3000
    # Working directory inside the container
    working_dir: /app
    # A bind mount exposes host contents to the container here.
    volumes:
      - ./app:/app
    environment:
      MYSQL_HOST: mysql
      MYSQL_USER: root
      MYSQL_PASSWORD: secret
      MYSQL_DB: todos
  mysql:
    image: mysql:5.7
    # A named volume persists container data on the host here.
    volumes:
      - todo-mysql-data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: secret
      MYSQL_DATABASE: todos
# Declare the named volume here; Compose does not create it automatically from the service's volume entry alone.
volumes:
  todo-mysql-data:

Startup

docker-compose up -d

Docker Compose logs showing network creation and application and MySQL container startup Application container logs showing a successful MySQL connection and listening on port 3000

The configuration creates two containers through Docker Compose.

It creates the configured volume and automatically creates a shared network.

Quickly Looking Up Docker Commands

Command reference. CLI, Dockerfile, and Compose reference entries in Docker documentation

Visual Operations with Docker Desktop

Docker Desktop makes it convenient to inspect Docker information locally. Docker Desktop container list and controls for startup, terminal access, and deletion It also lets you enter a container directly without typing the command yourself.

Example Code

Other Suggestions

As Docker becomes more widespread, we can change how we learn other software. Instead of installing it locally and configuring environment variables, pull a Docker image and start learning quickly. Delete the image afterward. Focus your energy on learning the software itself, without exhausting your enthusiasm on installation.


Share this post:

Previous Post
Getting Started with JMX: JMX Exporter Monitoring and OpenTelemetry Integration
Next Post
Using Log4j2 (Part 1)

Comments

Questions, corrections, and experiences are welcome. Sign in with GitHub to comment; both language versions share this discussion.

Comments are available on the live site only.