Docker's Common Use

In this post, I will cover Docker’s common use based on my experience.

I. Docker

1. Writing Dockerfile

The basic structure of Dockerfile

FROM <base_image_name>:<version>

# Copy some directories and files from the host file system into the container
COPY xxx xxx

# Run some commands
RUN xxx && xxx

# Specify the command to run
ENTRYPOINT ["executable", "param1", "param2"]

Common used instructions

Instructions for build
  • FROM
  • ARG
  • WORKDIR
  • COPY/ADD
  • RUN

FROM

Create a new build stage from a base image. For example,

FROM alpine:latest
FROM node:22-alpine

ARG

Create build-time variables. For example,

# define variable
ARG JAR_FILE=target/app.jar
# use variable
COPY ${JAR_FILE} app.jar

WORKDIR

Change the working directory. Like the cd command on Linux. For example,

WORKDIR /app

COPY

Copy files and directories from a source location on the host machine into the filesystem of the Docker image at a specified destination. You can add .dockerignore to ignore files. For example,

COPY . .

ADD

Copy local files and download the file from that URL. When you copy local files, it’s best to use COPY.

RUN

Run commands. It’s better to run multiple commands at once via && concatenation. For example,

RUN npm install
RUN ls -l
RUN mkdir /opt/app
RUN chmod +x init.sh
RUN ./init.sh
Instructions for container
  • ENV
  • EXPOSE
  • USER
  • ENTRYPOINT/CMD

ENV

Set environment variables for the container. For example,

ENV TZ=Asia/Shanghai

EXPOSE

Describe which ports your application is listening on. It is used in a Dockerfile to indicate which port(s) the container will listen on at runtime. While it does not actually publish the ports, it serves as documentation for anyone who uses the image and helps improve the clarity of how to run the container. For example,

EXPOSE 80

USER

Set user and group ID. For example,

# create user and group
RUN addgroup -S spring && adduser -S spring -G spring
# set user and group
USER spring:spring

ENTRYPOINT

Specify the default executable. Specifies the command that should always run; not overridden by runtime arguments. For example,

ENTRYPOINT ["java","-jar","/app.jar"]
  • Only can run java command.

CMD

Specify default commands. Specifies default arguments for ENTRYPOINT or acts as the command to run when no command is provided; can be overridden by runtime arguments. For example,

CMD ["java","-jar","/app.jar"]
  • By default, it runs java command. It can also run another command by overriding runtime arguments, for example, docker run my_container echo 'Hello World'.

More Dockerfile instructions to see: Dockerfile reference

Common used base images

Avoid Using the latest Tag. Specify exact versions of images for predictability and security. Using latest can lead to unexpected changes.

Linux

  • alpine:3 (3MB). A security-oriented, lightweight, and minimal Docker image based on Alpine Linux. It’s a popular choice for many applications due to its small size and package management system (apk).
FROM alpine:3

RUN apk add --update --no-cache package1 package2
FROM ubuntu:25.04

RUN apt-get update && \
apt-get install -y --no-install-recommends package1 package2 && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
FROM debian:12-slim

RUN apt-get update && \
apt-get install -y --no-install-recommends <your-package> && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*

Java

Note openjdk docker images are deprecated.

Node.js

Nginx

Python

Middleware

Other

  • scratch. 0 bytes (it’s an empty image). The scratch image is the ultimate minimal image. It is used to build completely statically compiled binaries (like Go applications). It is important to note that you can’t install any packages on scratch since it’s literally an empty image.
  • busybox:stable (1MB). BusyBox combines tiny versions of many common UNIX utilities into a single small executable. It’s often used for simple tasks and scripts. Tools that busybox provided: sh, file operations (ls, cp, mv, rm, mkdir, touch, etc), text processing (cat, echo, grep, etc), networking utilities (ifconfig, ping, wget, telnet, etc), system utilities (ps, kill, top, free), and so on.

Search Docker images

To get the size of a Docker image before a pull

# for an official image the namespace is called library
curl -s https://hub.docker.com/v2/repositories/library/alpine/tags | \
jq '.results[] | select(.name=="latest") | .full_size'

# here the project namespace is used
curl -s https://hub.docker.com/v2/repositories/mysql/mysql-server/tags | \
jq '.results[] | select(.name=="8.0") | .full_size'

Differences between ENTRYPOINT and CMD?

  • ENTRYPOINT : Specifies the command that should always run; not overridden by runtime arguments.
  • CMD : Specifies default arguments for ENTRYPOINT or acts as the command to run when no command is provided; can be overridden by runtime arguments.

For example:

Dockerfile

FROM ubuntu:20.04

# Use ENTRYPOINT to define the executable
ENTRYPOINT ["python3"]

# Use CMD to set the default script to run
CMD ["app.py"]
docker build -t my-python-image .

If you run the container without any command:

docker run my-python-image

It runs: python3 app.py.

If you want to run a different script instead:

docker run my-python-image another_script.py

It runs: python3 another_script.py.

Set timezone

ENV TZ=Asia/Shanghai

To run the application as a non-root user

# Change permissions of some directories and files in the mapping volume before changing to a non-root user.
RUN addgroup -S spring && adduser -S spring -G spring \
&& mkdir -p /app/uploadFile && chown -R spring:spring /app/uploadFile && chmod 755 /app/uploadFile \
&& mkdir /logs && chown -R spring:spring /logs && chmod 755 /logs
USER spring:spring

Multi-stage Dockerfile

You can define multiple build stages, and copy files from the previous stage. For example,

# Build Stage  
FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# Production Stage
FROM nginx:stable-alpine AS production
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

2. Building Docker Image

docker build -t <image_name> .

Commonly used options of docker build

  • -t, --tag: Specify image name.
  • --no-cache: Do not use cache when building the image. Automatically update to use the latest base image according to the version of the base image.
  • -f, --file: Dockerfile file path relative to docker context root path. By default, it’s <container_content_root_path>/Dockerfile. For example, ./Dockerfile.
  • --platform: Set target platform for build. For example, linux/amd64 (Linux), linux/arm64 (macOS).

For more options reference: Docker build documentation.

Note: Docker can’t translate the hostname of Docker containers during the build stage.

3. Running Docker Container

docker run -d --name <my_container> [,more_options] <image_name>

Common used options of docker run

  • Basic
    • -d, --detach: Run the container in the background.
    • --name: Set container name.
  • Environment
    • -e, --env: Set environment variables. For example, -e "SPRING_PROFILES_ACTIVE=prod". You can also specify multiple -e options to set multiple environment variables.
    • --env-file: Read in a file of environment variables. For example, --env-file .env
  • Disk
    • --mount: Attach a filesystem mount to the container. For detailed usage, see the content below.
    • -v, --volume: Bind mount a volume. For detailed usage, see the content below.
  • Network
    • --network: Connect a container to a network. Syntax: --network <network_name>. For example: --network my_app. You can also specify multiple --network options to connect multiple networks. Docker compose network: <project_directory_name>_default.
    • -p, --publish: Publish a container’s port(s) to the host. Syntax: -p host_port:container_port. For example, -p 8080:8080. You can also specify multiple -p options to publish multiple ports.
  • Interactive
    • -i, --interactive: Keep STDIN open even if not attached.
    • -t, --tty: Allocate a pseudo-TTY.
  • Others
    • --restart: Restart policy to apply when a container exits.
    • --rm: It is used to automatically remove a container after it has stopped running. This is particularly useful for running temporary containers that are only needed for a short period of time and helps keep your system clean by avoiding the accumulation of stopped containers.
    • -m, --memory: Memory limit. For example, -m 2GB.

For more options of docker run reference: docker container run

docker run -v

1. Using a named volume

Named volumes are managed by Docker, and volumes created this way can be shared among multiple containers.

docker run -v my-volume:/app/data myimage

2. Using a bind mount

A bind mount allows you to mount a specific directory or file from the host file system into the container.

docker run -v /path/on/host:/app/data myimage
  • /path/on/host is a file or directory on the host system.
  • /app/data is the directory inside the container where the host directory will be mounted.

3. Read-Only Mount

You can make a volume or bind mount read-only by appending :ro to the volume or bind mount specification.

docker run -v my-volume:/app/data:ro myimage

4. Multiple Mounts

You can also specify multiple -v options to mount multiple volumes or bind mounts into the same container.

docker run -v my-volume:/app/data -v /path/on/host:/app/config myimage

docker run –mount

Syntax

docker run --mount type=<type>,source=<source>,target=<target>[,readonly] <image>
  • type=<type>: The type of mount. Possible values are:
    • volume: For a Docker-managed volume.
    • bind: For a bind mount to a specific host path.
    • tmpfs: For a temporary filesystem mount in memory.
  • source=<source>: The source for the mount. This can be the name of a Docker volume (for type=volume) or a path on the host (for type=bind).
  • target=<target>: The path inside the container where the mount will be accessible.
  • readonly: Optional flag. If specified, the mount will be read-only for the container (i.e., no write operations will be allowed).
  • <image>: The name of the Docker image from which to create the container.

1. Using a Named Volume

docker run --mount type=volume,source=my-volume,target=/app/data myimage

2. Using a Bind Mount

docker run --mount type=bind,source=/path/on/host,target=/app/data myimage

3. Using a Tmpfs Mount

A tmpfs mount allows you to create a mount that resides in memory. This is useful for data that needs to be temporary.

docker run --mount type=tmpfs,target=/app/temp myimage
  • /app/temp is the directory inside the container where the temporary filesystem will be created.

4. Using Read-Only Mount

You can make any of the mounts read-only by adding the ,readonly option.

docker run --mount type=volume,source=my-volume,target=/app/data,readonly myimage

5. Multiple Mounts

You can specify multiple --mount options when starting a container to use multiple mounts.

docker run --mount type=volume,source=my-volume,target=/app/data \
--mount type=bind,source=/path/on/host,target=/app/config \
myimage

Run amd64 Docker image on arm64 (macOS)

Warning message

WARNING: The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested

Solutions

docker run --platform linux/amd64 ...

Run Docker container in interactive mode

docker run -it ...
  • -i, --interactive: Keep STDIN open even if not attached
  • -t, --tty: Allocate a pseudo-TTY

For example:

# connect to local postgres server container
docker run -it --network <postgres_network> jbergknoff/postgresql-client -h <postgres_container_name> -p 5432 -U postgres -d <DB_Name>

4. Stop and remove a Docker container

docker stop <container_name> || true && docker rm <container_name> || true

5. View logs of the Docker container

docker logs -f <container_name>

🚀II. Docker Compose

1. Writing Docker compose.yaml

The basic structure of compose.yaml

services:  
service1:
image: '<image_name>:<version>'
container_name: '<custom_container_name>'
networks:
- network1
- network2
environment:
- <name>=<value>
env_file: '.env'
volumes:
- '<docker_volume>:/var/lib/xxx/data'
- '<host_absolute_filepath>:<container_absolute_filepath>'
ports:
- <host_port>:<container_port>
service2:
image: '<image_name>:<version>'
container_name: '<custom_container_name>'
service3:
container_name: '<custom_container_name>'
build:
context: .
dockerfile: Dockerfile


volumes:
volume1:
driver: local
volume1:
driver: local

networks:
network1:
network2:

2. Starting Docker Compose containers

3. Rebuild images in Docker compose