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 <host_path> <container_path>

# Run some commands
RUN <command1> && <command2> && ...

# 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
# for example
RUN apk add --update --no-cache bash
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.
    • --read-only: Mount the container’s root filesystem as read only.
  • Network
    • --net, --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
    • --platform: Set platform if server is multi-platform capable.
    • --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.
    • --pull: Pull image before running (always, missing, never). The default value is missing.
    • -m, --memory: Memory limit. For example, -m 2GB.

For more options of docker run reference: docker container run documentation

–restart policies

The most commonly used restart policy is unless-stopped.

Flag Description
no Don’t automatically restart the container. (Default)
on-failure[:max-retries] Restart the container if it exits due to an error, which manifests as a non-zero exit code. Optionally, limit the number of times the Docker daemon attempts to restart the container using the :max-retries option. The on-failure policy only prompts a restart if the container exits with a failure. It doesn’t restart the container if the daemon restarts.
always Always restart the container if it stops. If it’s manually stopped, it’s restarted only when Docker daemon restarts or the container itself is manually restarted.
unless-stopped Similar to always, except that when the container is stopped (manually or otherwise), it isn’t restarted even after Docker daemon restarts.

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>

Restart one or more containers

docker restart [, options] <container>

4. View logs of the Docker container

docker logs -f <container_name>

5. Stop and remove a Docker container

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

6. Execute a command in a running container

Execute a command in a running container

docker exec <container_name> <command>
docker exec my_app sh
# interactive mode
docker exec -it <container_name> <command>

7. More Docker commands

Copy files/folders between a container and the local filesystem

# Copy a local file into container
docker cp ./some_file CONTAINER:/work
docker exec CONTAINER ls -la /work

# Copy files from container to local path
docker cp CONTAINER:/var/logs/ /tmp/app_logs

# Copy a file from container to stdout. Note `cp` command produces a tar stream
docker cp CONTAINER:/var/logs/app.log - | tar x -O | grep "ERROR"

Display detailed information on one or more containers

docker inspect [,options] <container>

Display a live stream of container(s) resource usage statistics

docker stats [,options] <container>

List port mappings or a specific mapping for the container

docker port [,options] <container>

Display the running processes of a container

docker top [,options] <container>

List containers

docker ls

Remove all stopped containers

docker prune

For more Docker commands reference: docker container documentation

II. Docker Compose

1. Writing Docker compose.yaml

The basic structure of compose.yaml

name: <project_name>

services:
service1:
image: <image_name>:<version>
container_name: <custom_container_name>
ports:
- <host_port>:<container_port>
environment:
- <name>=<value>
env_file: .env
volumes:
- '<docker_volume>:/var/lib/xxx/data'
- '<host_path>:<container_absolute_path>'
networks:
- network1
- network2

service2:
image: <image_name>:<version>
container_name: <custom_container_name>
service3:
container_name: <custom_container_name>
build:
context: .
dockerfile: Dockerfile


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

networks:
# Define a network with the bridge driver type
network1:
driver: bridge
# using the default network driver type: bridge
network2:

Commonly used attributes/properties of docker-compose.yaml

  • name: Project name. By default, it’s project directory name.
  • services:
    • image: Image name and tag. For example, image: redis:7-alpine
    • build: specifies the build configuration for creating a container image from source
    • container_name: Set custom container name. For example, container_name: my-app.
    • ports: Define the port mappings between the host machine and the containers. Syntax: Ports: "<host_port>:<container_port>". For example, ports: "8080:8080"
    • env_file: Specify one or more files that contain environment variables to be passed to the containers. For example, env_file: .env
    • environment: Defines environment variables set in the container. For example, environment: TZ=Asia/Shanghai
    • volumes: define mount host paths or named volumes that are accessible by service containers.
    • networks: networks that service containers are attached to. For example: networks: my-network
    • Some more
      • depends_on: Define dependencies between services. It specifies the order in which services should be started and can also define the order of shutdown behavior.
      • platform: The target platform the containers for the service run on. For example, linux/amd64 (Linux), linux/arm64 (macOS).
      • read_only: Configures the service container to be created with a read-only filesystem.
      • restart: Defines the policy that the platform applies on container termination.

For more properties reference: Compose file reference

Note

  • Port mapping must not be used with network_mode: host. Doing so causes a runtime error because network_mode: host already exposes container ports directly to the host network, so port mapping isn’t needed.

Docker Compose Options

  • --env-file: Specify an alternate environment file
  • -f, --file: Compose configuration files
  • --project-directory: Specify an alternate working directory (default: the path of the, first specified, Compose file)
  • -p, --project-name: Project name

For example:

docker compose -f xxx up -d

2. Starting Docker Compose containers

docker compose up [, options...]

Commonly used options of docker compose up

  • -d, --detach: Detached mode: Run containers in the background.
  • --build: Build images before starting containers. If containers in docker-compose.yaml has been created, docker will not build image even there are some changes. Running with --build ensures that the latest code or dependencies are included in the new image.
  • --force-recreate: Recreate containers even if their configuration and image haven’t changed.
  • --pull: Pull image before running (“always”|”missing”|”never”). For example, --pull always
  • -w, --watch: Watch source code and rebuild/refresh containers when files are updated. Monitor changes in your files (like source code or configuration files) and automatically rebuild and restart the defined services when changes are detected.
  • --remove-orphans: Remove containers for services not defined in the Compose file.

For more options reference: docker compose up documentation

Restart service containers

docker compose restart

View output from containers

docker compose logs

3. Build Docker Compose image (Optional)

docker compose up can build image automatically if images doesn’t exist.

docker compose build [, options...]

docker compose build will read your docker-compose.yml, look for all services containing the build: statement and run a docker build for each one.

Commonly used options of docker compose build

  • --build-arg: Set build-time variables for services.
  • --no-cache: Do not use cache when building the image.
  • --pull: Always attempt to pull a newer version of the image.

For more options reference: docker compose build documentation

4. Stop and remove Docker Compose containers

Stop services

docker compose stop

Force stop service containers

docker compose kill

Stop and remove containers, networks

docker compose down

Removes stopped service containers

docker compose rm [OPTIONS] [SERVICE...]

5. Execute a command in a running container

Execute a command in a running container

docker compose exec [, options] <service> <command>
docker compose exec my_service_1 sh
# interative mode
docker compose exec -it [, options] <service> <command>

Run a one-off command on a service

docker compose run [OPTIONS] SERVICE [COMMAND] [ARGS...]

For examples:

docker compose run web bash
docker compose run db psql -h db -U docker

6. More Docker Compose Commands

Copy files/folders between a service container and the local filesystem

# Copy a local file into container
docker compose cp ./some_file SERVICE:/work
# Verify
docker compose exec SERVICE ls -al /work

# Copy files from container to local path
docker compose cp SERVICE:/var/logs/ /tmp/app_logs

Pull service images

docker compose pull

List images used by the created containers

docker compose images

List running compose projects

docker compose ls

List containers

docker compose ps

Display the running processes

docker compose top

More Docker Compose commands reference: docker compose documentation

III. Storage

Volumes

Volumes are persistent data stores for containers, created and managed by Docker. You can create a volume explicitly using the docker volume create command, or Docker can create a volume during container or service creation.

Volumes are the preferred mechanism for persisting data generated by and used by Docker containers. While bind mounts are dependent on the directory structure and OS of the host machine, volumes are completely managed by Docker. Volumes are a good choice for the following use cases:

  • Volumes are easier to back up or migrate than bind mounts.
  • You can manage volumes using Docker CLI commands or the Docker API.
  • Volumes work on both Linux and Windows containers.
  • Volumes can be more safely shared among multiple containers.
  • New volumes can have their content pre-populated by a container or build.
  • When your application requires high-performance I/O.

Volumes are not a good choice if you need to access the files from the host, as the volume is completely managed by Docker. Use bind mounts if you need to access files or directories from both containers and the host.

Mount a Docker volume into a container when starting a container

docker run -v <volume-name>:<container-path>

Mount a Docker volume in compose.yaml

services:  
service1:
# ...
volumes:
- '<volume-name>:<container_absolute_path>'

volumes:
<volume-name>:
driver: local

Bind mounts

When you use a bind mount, a file or directory on the host machine is mounted from the host into a container.

Bind mounts are appropriate for the following types of use case:

  • Sharing source code or build artifacts between a development environment on the Docker host and a container.
  • When you want to create or generate files in a container and persist the files onto the host’s filesystem.
  • Sharing configuration files from the host machine to containers. This is how Docker provides DNS resolution to containers by default, by mounting /etc/resolv.conf from the host machine into each container.

Bind-mounting over existing data

If you bind mount file or directory into a directory in the container in which files or directories exist, the pre-existing files are obscured by the mount.

Bind mount when starting a container

docker run --volume <host-path>:<container-path>:[,ACCESS_MODE]
# or
docker run -v <host-path>:<container-path>:[,ACCESS_MODE]

For example:

docker run -v /data:/data
dokcer run -v ./config:/app/config:ro

Bind mount in compose.yaml

services:  
service1:
# ...
volumes:
- <host-path>:<container-path>:<,ACCESS_MODE>

Access mode

  • rw: Read and write access. This is the default if none is specified.
  • ro: Read-only access.
  • z: SELinux option indicating that the bind mount host content is shared among multiple containers.
  • Z: SELinux option indicating that the bind mount host content is private and unshared for other containers.

tmpfs mounts

If you’re running Docker on Linux, you have a third option: tmpfs mounts. When you create a container with a tmpfs mount, the container can create files outside the container’s writable layer.

As opposed to volumes and bind mounts, a tmpfs mount is temporary, and only persisted in the host memory. When the container stops, the tmpfs mount is removed, and files written there won’t be persisted.

tmpfs mounts are best used for cases when you do not want the data to persist either on the host machine or within the container. This may be for security reasons or to protect the performance of the container when your application needs to write a large volume of non-persistent state data.

Mounting over existing data

If you create a tmpfs mount into a directory in the container in which files or directories exist, the pre-existing files are obscured by the mount.

Limitations of tmpfs mounts

  • Unlike volumes and bind mounts, you can’t share tmpfs mounts between containers.
  • This functionality is only available if you’re running Docker on Linux.
  • Setting permissions on tmpfs may cause them to reset after container restart. In some cases setting the uid/gid can serve as a workaround.

Bind tmpfs mount when starting a container

# bind tmpfs mount mount to /app in the container
docker run --mount type=tmpfs,destination=/app <image_name>

Bind tmpfs mount in compose.yaml

services:  
service1:
# ...
volumes:
- type: tmpfs
target: /data
tmpfs:
size: "12884901888"

For more about Docker storage reference: Storage - Docker documentation

IV. Networking

User-defined networks

You can create custom, user-defined networks, and connect multiple containers to the same network. Once connected to a user-defined network, containers can communicate with each other using container IP addresses or container names.

Network driver types

Driver Description
bridge The default network driver.
host Remove network isolation between the container and the Docker host.
none Completely isolate a container from the host and other containers.
overlay Overlay networks connect multiple Docker daemons together.
ipvlan IPvlan networks provide full control over both IPv4 and IPv6 addressing.
macvlan Assign a MAC address to a container.

Connect to networks when starting a container

docker run --network=my-network <image_name>

Set networks in compose.yaml

services:  
service1:
#...
# connect to networks
networks:
- network1
- network2
service2:
#...

networks:
# Define a network with the bridge driver type
network1:
driver: bridge
# using the default network driver type: bridge
network2:

Host network driver

If you use the host network mode for a container, that container’s network stack isn’t isolated from the Docker host (the container shares the host’s networking namespace), and the container doesn’t get its own IP-address allocated. For instance, if you run a container which binds to port 80 and you use host networking, the container’s application is available on port 80 on the host’s IP address.

Host mode networking can be useful for the following use cases:

  • To optimize performance
  • In situations where a container needs to handle a large range of ports

Using the host network mode when starting a container

docker run --net=host <image_name>
# or
docker run --network=host <image_name>

Using the host network mode in compose.yaml

services:  
service1:
#...
network_mode: "host"
service2:
#...

For more about Docker network reference: Networking - Docker documentation