DevOpsLesson
DevOpsLesson

Free, comprehensive DevOps tutorials and learning roadmaps. Master Docker, Kubernetes, CI/CD, and more.

Stay Updated

Get notified about new tutorials and features.

Tutorials

  • What is DevOps?
  • Docker Tutorial
  • Terraform Tutorial
  • CI/CD Pipeline
  • All Tutorials

Roadmaps

  • DevOps Engineer
  • Cloud Engineer
  • SRE Path
  • All Roadmaps

Company

  • About Us
  • Blog
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 DevOpsLesson. All rights reserved.

DOCKERKUBERNETESTERRAFORMAWSCI/CDLINUXGITDEVOPS ROADMAPCLOUD ROADMAPSRE ROADMAPGIT CHEATSHEETDOCKER CHEATSHEETK8S CHEATSHEETTF CHEATSHEETLINUX CHEATSHEETDOCKERFILE LINTERYAML VALIDATORCRON PARSERREGEX TESTER

Docker Tutorial

Introduction to docker
Why Use Docker?
Docker vs Virtual Machines
Installing Docker
Key Docker Concepts
Docker Images
Docker Containers
Writing Dockerfiles
Docker Volumes

WORKDIR & EXPOSE

PreviousPrev
Next

Set working directories cleanly and document ports correctly. Plus LABEL, USER, VOLUME, and HEALTHCHECK - the supporting instructions every production Dockerfile needs.

WORKDIR & EXPOSE

WORKDIR and EXPOSE are the two most frequently used supporting instructions. This page also covers LABEL, USER, VOLUME, and HEALTHCHECK - the smaller instructions that complete a production-grade Dockerfile.

WORKDIR - Set the Working Directory

WORKDIR sets the current directory for all subsequent RUN, COPY, ADD, CMD, and ENTRYPOINT instructions. It also becomes the default directory when you docker exec into a container.

WORKDIR /app

# All subsequent instructions are relative to /app
COPY package.json .       # → /app/package.json
RUN npm install           # runs in /app
CMD ["node", "server.js"] # runs /app/node.js

Creates the directory if it doesn't exist - no need for RUN mkdir -p /app.

Use absolute paths

#  Absolute path - always clear
WORKDIR /app

# Relative path - confusing, depends on previous WORKDIR
WORKDIR app

Multiple WORKDIRs are fine

WORKDIR /app
COPY package*.json ./
RUN npm install

WORKDIR /app/config     # Now in /app/config
COPY ./config .

WORKDIR /app            # Back to /app
CMD ["node", "server.js"]

Each WORKDIR stacks - but for clarity, minimize context-switching and prefer staying in one directory.

Don't use RUN cd

# This cd only applies within that one RUN command
RUN cd /app && npm install

#  WORKDIR persists across instructions
WORKDIR /app
RUN npm install

EXPOSE - Document Ports

EXPOSE tells Docker (and humans reading the Dockerfile) which network ports the container listens on. It is documentation only - it does not actually publish the port or make it reachable from the host.

EXPOSE 3000          # TCP (default)
EXPOSE 8080/tcp      # Explicit TCP
EXPOSE 53/udp        # UDP port
EXPOSE 80 443        # Multiple ports

To actually publish a port to the host, use -p at docker run time:

# EXPOSE 3000 in Dockerfile + this flag = host can reach it
docker run -p 8080:3000 myapp

# EXPOSE alone does nothing for host access
docker run myapp   # Port 3000 is NOT reachable from host

So why use EXPOSE? Three reasons:

  1. Documents the interface - tells users and tools what port to map
  2. Used by docker run -P (capital P) to auto-publish all exposed ports to random host ports
  3. Used by Docker Compose and orchestration tools to wire services together

LABEL - Image Metadata

LABEL adds key-value metadata to an image. It's searchable and visible in docker inspect.

LABEL maintainer="platform-team@example.com"
LABEL version="2.1.0"
LABEL description="Production API server"

# OCI standard labels (recommended)
LABEL org.opencontainers.image.source="https://github.com/org/repo"
LABEL org.opencontainers.image.version="2.1.0"
LABEL org.opencontainers.image.created="2026-02-04T10:00:00Z"
LABEL org.opencontainers.image.authors="platform-team@example.com"
LABEL org.opencontainers.image.licenses="MIT"

Using the OCI Image Spec labels makes your images compatible with tooling that reads standard metadata.

Inspect labels:

docker inspect myapp --format='{{json .Config.Labels}}' | python3 -m json.tool

USER - Run as Non-Root

By default, containers run as root. This is a security risk - if an attacker escapes the container, they have root on the host. Always switch to a non-root user before the final CMD.

FROM node:20-alpine
WORKDIR /app

COPY package*.json ./
RUN npm ci --omit=dev
COPY --chown=node:node . .

# Switch to the built-in 'node' user (UID 1000)
USER node

CMD ["node", "server.js"]

Many official images already include a non-root user:

  • node image → node user
  • nginx image → nginx user
  • postgres image → postgres user

For custom images, create the user explicitly:

# Alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser

# Debian/Ubuntu
RUN groupadd -r appgroup && useradd -r -g appgroup appuser
USER appuser

You can temporarily step back to root during the build:

USER root
RUN apt-get install -y something
USER appuser

VOLUME - Declare Mount Points

VOLUME declares that a directory should be externally mountable - it marks a path as a persistence point.

VOLUME /app/data
VOLUME /var/log/myapp
VOLUME ["/app/data", "/app/logs"]  # Multiple at once

When Docker creates a container from an image with VOLUME, it automatically creates an anonymous volume for each declared path if no volume is explicitly provided at docker run time.

# Docker auto-creates an anonymous volume for /app/data
docker run myapp

# Or provide a named volume explicitly
docker run -v mydata:/app/data myapp

Important: VOLUME only matters for data that must survive the container lifecycle. Don't use it for configuration or static files - those should be in the image layers.

HEALTHCHECK - Container Health Status

HEALTHCHECK defines a command Docker runs periodically to check if the container is healthy. It changes the container's status between starting, healthy, and unhealthy.

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

# TCP check (nc)
HEALTHCHECK --interval=10s --timeout=3s --retries=5 \
  CMD nc -z localhost 5432 || exit 1

# Custom script
HEALTHCHECK CMD /app/scripts/healthcheck.sh

Options:

  • --interval - How often to run (default: 30s)
  • --timeout - Max time for the check to complete (default: 30s)
  • --start-period - Grace period before failures count (default: 0s)
  • --retries - Failures before marking unhealthy (default: 3)

Check health status:

docker ps   # STATUS column shows (healthy) or (unhealthy)
docker inspect --format='{{.State.Health.Status}}' myapp

Orchestrators like Docker Swarm and Kubernetes use health checks to decide whether to restart a container or route traffic to it.

Exercise

Set WORKDIR, EXPOSE, and USER

You are completing a Dockerfile based on node:20-alpine. After installing dependencies and copying the source (owned by node:node), you need to: document that the app listens on port 3000, switch to the non-root "node" user, and start the app with "node server.js" using exec form. Which set of instructions, in order, is correct?

Summary

  • WORKDIR sets the working directory for all subsequent instructions - use absolute paths, never RUN cd
  • EXPOSE documents ports but doesn't publish them - you still need -p at runtime
  • LABEL adds searchable metadata - use OCI standard labels for tooling compatibility
  • USER switches to a non-root user - always do this before CMD in production images
  • VOLUME declares mount points - Docker auto-creates anonymous volumes for them if none are provided
  • HEALTHCHECK lets orchestrators know if your container is actually functioning, not just running
PreviousPrev
Next

Continue Learning

RUN

Execute commands at build time. Learn shell vs exec form, how to keep layers lean, combine commands efficiently, and avoid bloated images.

8 min·Easy

CMD & ENTRYPOINT

Define how your container starts. Understand exec vs shell form, the difference between CMD and ENTRYPOINT, and how to combine them for flexible, production-ready containers.

9 min·Easy

ENV & ARG

Inject variables into your builds and containers. Learn the critical difference between ENV (runtime) and ARG (build-time only), and how to use both safely.

7 min·Easy

Explore Related Topics

Ku

Kubernetes Tutorials

Orchestrate your Docker containers at scale

CI

CI/CD Tutorials

Automate Docker builds and deployments in pipelines

Try the Tool

Dockerfile Linter

Instantly lint your Dockerfile for best-practice violations and security issues.

On This Page

WORKDIR & EXPOSE`WORKDIR` - Set the Working Directory`EXPOSE` - Document Ports`LABEL` - Image Metadata`USER` - Run as Non-Root`VOLUME` - Declare Mount Points`HEALTHCHECK` - Container Health StatusSummary