Advanced Dockerfile Commands

Production-grade Dockerfiles must not only run, but also be secure, configurable, and monitorable—all of which are achieved through advanced instructions.

1. What You'll Learn


2. A Story About a Security Audit

(1) Pain Point: Running containers as root is a high-risk vulnerability

Charlie's security audit report notes that "containers running as root" is a high-risk vulnerability. If an attacker gains access to a container through an application vulnerability, they will have root privileges and can modify system files and mount host directories. The audit score is only 45 points.

(2) Solutions for Securing Dockerfiles

Alice added a line USER appuser to the Dockerfile, raising the security score from 45 to 85.

DOCKERFILE
# Security hardening: run as non-root user
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .

# Create and switch to non-root user
RUN useradd -m appuser
USER appuser

CMD ["python", "app.py"]

(3) Benefits: Double the security score

I added just two lines (RUN useradd + USER), and the security score went from 45 to 85, passing the audit.


3. COPY vs ADD

(1) Feature Comparison

Function COPY ADD
Copy local files to the image
Download file from URL
Automatically extract tar/gzip/bzip2/xz
Multi-stage Build --from

(2) Selection Principles

📌 Key Point: The best practice is to use only COPY. The automatic extraction and URL download behavior of ADD is unpredictable and prone to errors. When extraction is needed, explicitly use the RUN tar command for greater transparency and control.

▶ Example: Differences in Behavior Between COPY and ADD (Difficulty: ⭐⭐)

DOCKERFILE
# COPY: simply copies the tar file as-is
COPY archive.tar.gz /tmp/
# Result: /tmp/archive.tar.gz (still compressed)

# ADD: automatically extracts the tar file
ADD archive.tar.gz /tmp/
# Result: /tmp/archive/ (extracted contents)

▶ Example: Multi-stage build with COPY --from (Difficulty: ⭐⭐⭐)

DOCKERFILE
# Multi-stage: copy artifact from builder stage
FROM golang:1.22 AS builder
WORKDIR /src
COPY . .
RUN go build -o server

FROM alpine:3.19
COPY --from=builder /src/server /usr/local/bin/server
CMD ["server"]

  1. ARG and ENV

(1) Comparison of Scope of Application

Dimension ARG ENV
Available Stages Build time (docker build) Runtime (docker run)
Persistence Do not write to image metadata Write to image metadata
Coverable --build-arg docker run -e
CMD/ENTRYPOINT Available
Security Not exposed at runtime Exposed at runtime (visible via docker inspect)
100%
graph TB
    BUILD["Build Time"] -->|ARG| LAYER["Image Layer"]
    RUN["Run Time"] -->|ENV| CONTAINER["Container Env"]
    BUILD -->|ENV persists| CONTAINER

▶ Example: ARG Version Number Construction (Difficulty: ⭐⭐)

DOCKERFILE
# Use ARG for build-time variables
ARG PYTHON_VERSION=3.12
FROM python:${PYTHON_VERSION}-slim

ARG APP_VERSION=1.0
LABEL version="${APP_VERSION}"

WORKDIR /app
COPY . .
CMD ["python", "app.py"]
BASH
# Build with custom Python version
docker build --build-arg PYTHON_VERSION=3.11 -t myapp:3.11 .

# Build with custom app version
docker build --build-arg APP_VERSION=2.0 -t myapp:v2.0 .

▶ Example: ENV Environment Variable Configuration (Difficulty: ⭐⭐)

DOCKERFILE
# ENV: available at runtime
FROM python:3.12-slim

ENV APP_PORT=5000 \
    APP_HOST=0.0.0.0 \
    LOG_LEVEL=info

WORKDIR /app
COPY . .
CMD ["python", "app.py"]
BASH
# Override ENV at runtime
docker run -d -e APP_PORT=8080 -e LOG_LEVEL=debug myapp:1.0
⚠️ Note: Do not store sensitive information (passwords/keys) in ENV variables, as they will be written to the image metadata and can be viewed by anyone docker inspect. Inject sensitive information at runtime using docker run -e, or use Docker Secrets.


5. WORKDIR Working Directory

▶ Example: WORKDIR—Setting the Working Directory (Difficulty: ⭐)

DOCKERFILE
# WORKDIR sets the working directory for subsequent instructions
FROM python:3.12-slim

# Each WORKDIR creates the directory if it doesn't exist
WORKDIR /app
COPY . .

# WORKDIR can be relative (relative to previous WORKDIR)
WORKDIR src
RUN pwd  # Output: /app/src
Rule Description
Use absolute paths WORKDIR /app instead of WORKDIR app
Create automatically Create automatically if the directory does not exist
Affects RUN/CMD/ENTRYPOINT/COPY Subsequent commands are executed based on WORKDIR
Do not use RUN cd RUN cd /app && ... is only valid for the current RUN; WORKDIR remains in effect

6. Security Principles for Non-root Users

(1) Risks of Running Containers as root

Risk Description
Privilege Escalation Container vulnerability + root privileges = Attacker gains root privileges on the host
File Modification Any file within the container (including system files) can be modified
Mounting Risks When mounting a host directory, the root container can modify host files
CIS Benchmark "Containers run as non-root" is a mandatory requirement in the CIS Docker Security Baseline

▶ Example: Running as a non-root USER (Difficulty: ⭐⭐)

DOCKERFILE
# Best practice: create a dedicated user and switch to it
FROM python:3.12-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .

# Create non-root user with specific UID/GID
RUN groupadd -r appgroup && \
    useradd -r -g appgroup -u 1000 -m appuser

# Ensure the app user owns the application files
RUN chown -R appuser:appgroup /app

# Switch to non-root user
USER appuser

CMD ["python", "app.py"]
🔒 Security: All commands (RUN/CMD/ENTRYPOINT) following USER appuser are executed as appuser. Even if an attacker gains access to the container, they will not have root privileges.


7. EXPOSE Port Declaration

▶ Example: EXPOSE Port Declaration (Difficulty: ⭐)

DOCKERFILE
# EXPOSE documents which ports the container listens on
FROM python:3.12-slim
WORKDIR /app
COPY . .
EXPOSE 5000
CMD ["python", "app.py"]
⚠️ Note: EXPOSE is a documentation declaration; it does not actually expose the port. Container port mapping still requires docker run -p 5000:5000. The purpose of EXPOSE is: ① Documentation; ② docker run -P automatically selects ports declared with EXPOSE when performing random mapping.


8. HEALTHCHECK Health Check

HEALTHCHECK allows Docker to automatically check whether applications inside containers are healthy and to automatically restart them if they are not.

(1) Health Check Syntax

DOCKERFILE
HEALTHCHECK [OPTIONS] CMD command
Option Default Description
--interval 30s Check Interval
--timeout 30s Timeout
--start-period 0s Container startup grace period
--retries 3 Mark as "unhealthy" after X consecutive failures

▶ Example: HEALTHCHECK curl check (Difficulty: ⭐⭐)

DOCKERFILE
# Health check with curl
FROM python:3.12-slim

WORKDIR /app
COPY . .
RUN apt-get update && \
    apt-get install -y --no-install-recommends curl && \
    rm -rf /var/lib/apt/lists/*

EXPOSE 5000

HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
  CMD curl -f http://localhost:5000/health || exit 1

CMD ["python", "app.py"]
BASH
# Check health status
docker inspect myapp --format='{{.State.Health.Status}}'
💻 Output:

TEXT
healthy

(1) Health Status Description

Status Meaning Docker Behavior
starting During the grace period (start-period) Not counted
healthy Passed inspection Operating normally
unhealthy Failed retries in a row Triggers an alert; can automatically restart when combined with a restart policy

9. LABEL Metadata

▶ Example: Adding Metadata to LABEL (Difficulty: ⭐)

DOCKERFILE
# LABEL adds metadata to the image
LABEL maintainer="alice@example.com"
LABEL version="1.0"
LABEL description="Flask web application for production"
LABEL org.opencontainers.image.source="https://github.com/example/app"

10. Complete Example: Writing a Secure Dockerfile for a Spring Boot Application

DOCKERFILE
# ============================================
# Production-grade Dockerfile for Spring Boot
# Features: non-root user, HEALTHCHECK, ARG, LABEL
# ============================================

# Build argument for JAR file name
ARG JAR_FILE=app.jar

# Stage 1: Build (future multi-stage, here just copy)
FROM eclipse-temurin:21-jre-alpine

# Metadata
LABEL maintainer="dev-team@example.com"
LABEL version="2.0"

# Create non-root user
RUN addgroup -S appgroup && \
    adduser -S appuser -G appgroup

# Set working directory
WORKDIR /app

# Copy the JAR file (use ARG for flexibility)
COPY target/${JAR_FILE} app.jar

# Ensure app user owns the files
RUN chown -R appuser:appgroup /app

# Switch to non-root user
USER appuser

# Expose application port
EXPOSE 8080

# Health check
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
  CMD wget -qO- http://localhost:8080/actuator/health || exit 1

# Run the application
ENTRYPOINT ["java", "-jar", "app.jar"]
CMD ["--server.port=8080"]
BASH
# Build with custom JAR name
docker build --build-arg JAR_FILE=myapp-2.0.jar -t spring-app:2.0 .

# Run with environment override
docker run -d -p 8080:8080 --name spring spring-app:2.0

# Verify health status
docker inspect spring --format='User: {{.Config.User}}, Health: {{.State.Health.Status}}'
💻 Output:

TEXT
User: appuser, Health: healthy

❓ FAQ

Q Can ADD automatically extract tar archives?
A Yes, but this is a "trap feature"—if you just want to copy a tar file (without extracting it), ADD will unexpectedly extract it. Best practice: Use COPY exclusively, and explicitly use RUN tar -xzf file.tar.gz when you need to extract, to ensure transparent and controllable behavior.
Q What is the difference between ARG and ENV?
A ARG is only available during the build process and is not written to the image; ENV is written to the image and is also visible at runtime. A simple rule of thumb: use ENV for anything that needs to be accessible at runtime (such as port configuration), and use ARG for anything used only during the build (such as version numbers or download URLs). Use the runtime -e to store passwords, not ENV.
Q How should I configure HEALTHCHECK to avoid false positives?
A Three key points: ① Set --start-period to allow time for the application to start up (at least 30 seconds for Java applications); ② Check the application’s actual health endpoint (e.g., /health); don’t just verify that the port is accessible; ③ Ensure --timeout does not exceed half of --interval to avoid a backlog of checks.
Q Why shouldn’t containers be run as root?
A When a container runs as root, an attacker who gains access to the container through an application vulnerability will obtain root privileges. Combined with a kernel vulnerability, this could lead to an escape to the host. Running as a non-root user significantly reduces the attack surface—the CIS Docker Benchmark lists “run as non-root” as a mandatory requirement.
Q Does EXPOSE actually expose ports?
A No. EXPOSE is merely a documentation declaration that tells users which ports the container intends to use. Actual mapping requires docker run -p or -P. -P (in uppercase) automatically maps the ports declared by EXPOSE to random ports on the host.
Q How can I verify that the container is running as a non-root user?
A docker exec <container> whoami should return a username other than root. Or use docker inspect <container> --format='{{.Config.User}}' to view the configured user.

📖 Summary


📝 Exercises

  1. Basic Exercise (Difficulty ⭐): Add a non-root user to the Dockerfile in Lesson 7. After building the image, use docker exec to verify that whoami does not output "root."
  2. Advanced Exercise (Difficulty ⭐⭐): Add the HEALTHCHECK directive and use curl to check the application’s health endpoint. After building and running the application, use docker inspect to view Health.Status.
  3. Challenge (Difficulty: ⭐⭐⭐): Write a complete, production-grade Dockerfile for the Flask application from Lesson 7, including: ARG version number + ENV configuration + non-root user + HEALTHCHECK + LABEL. Then, use docker scout quickview or trivy image to scan the image for security vulnerabilities.
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏