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

CMD & ENTRYPOINT

PreviousPrev
Next

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.

CMD & ENTRYPOINT

CMD and ENTRYPOINT both define what runs when a container starts - but they serve different roles and interact in ways that confuse most people at first. Getting this right determines how flexible and well-behaved your container is.

Two Forms: Shell vs Exec

Both instructions support two syntaxes. The choice matters significantly.

Shell form

CMD node server.js
ENTRYPOINT python app.py

Docker wraps the command in /bin/sh -c "...". This means:

  • Environment variable expansion works ($PORT, $HOME)
  • Your process is NOT PID 1 - /bin/sh is
  • Shell signals (SIGTERM) aren't forwarded to your process
  • docker stop may not work cleanly

Exec form (preferred for CMD and ENTRYPOINT)

CMD ["node", "server.js"]
ENTRYPOINT ["python", "app.py"]

Docker runs the process directly (no shell wrapper). This means:

  • Your process IS PID 1 - it receives OS signals directly
  • docker stop sends SIGTERM directly to your process
  • Graceful shutdown works correctly
  • No variable expansion (use ENV for that)

Always use exec form for CMD and ENTRYPOINT in production.

CMD - Overridable Default

CMD sets the default command to run. It can be completely replaced at docker run time:

FROM node:20-alpine
WORKDIR /app
COPY . .
CMD ["node", "server.js"]
# Uses CMD: runs node server.js
docker run myapp

# Overrides CMD: runs node --inspect server.js instead
docker run myapp node --inspect server.js

# Overrides CMD entirely: opens a shell
docker run -it myapp sh

This is CMD's defining characteristic: it's a default that users can replace.

ENTRYPOINT - Fixed Executable

ENTRYPOINT makes the container behave like a specific executable. Arguments passed at docker run are appended to it - they don't replace it:

FROM alpine
ENTRYPOINT ["ping"]
CMD ["localhost"]   # Default argument
# Uses CMD as default argument: ping localhost
docker run ping-container

# Appends to ENTRYPOINT: ping google.com
docker run ping-container google.com

# Can't just run 'sh' - it becomes 'ping sh'
docker run ping-container sh   # Wrong

To override ENTRYPOINT, you must use --entrypoint:

docker run --entrypoint sh -it ping-container   #

CMD + ENTRYPOINT Together

The most powerful pattern - ENTRYPOINT fixes the executable, CMD provides overridable default arguments:

FROM nginx:alpine
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]   # Default flags
# Default: nginx -g "daemon off;"
docker run my-nginx

# Override just the flags
docker run my-nginx -g "daemon off;" -c /custom/nginx.conf

Another real-world example:

FROM python:3.12-slim
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt

ENTRYPOINT ["python", "manage.py"]
CMD ["runserver", "0.0.0.0:8000"]
# Start the dev server (uses CMD defaults)
docker run mydjango

# Run migrations instead
docker run mydjango migrate

# Open a shell
docker run mydjango shell

# Run tests
docker run mydjango test

The container is now a manage.py wrapper - everything passed to docker run becomes a subcommand.

Exercise

CMD vs ENTRYPOINT

You have built an image "echo-test" from a Dockerfile with ENTRYPOINT ["echo"] and CMD ["Hello from CMD"]. You want to first run it with no arguments to see the default message, then run it again to print "Hello from args!" instead. Which sequence of commands is correct?

Only One CMD / ENTRYPOINT Is Used

If you write multiple CMD or ENTRYPOINT instructions, only the last one takes effect:

CMD ["node", "server.js"]
CMD ["node", "worker.js"]   # ← Only this one is used

This is often an accidental bug in long Dockerfiles. Keep only one of each.

Signal Handling and PID 1

When your process runs as PID 1 (exec form), it receives signals directly. But PID 1 has special responsibilities - it must handle zombie processes if it spawns children.

For complex applications that fork child processes, use a minimal init system:

FROM node:20-alpine

# Install tini - a minimal init system
RUN apk add --no-cache tini

WORKDIR /app
COPY . .
RUN npm ci --omit=dev

# tini runs as PID 1 and forwards signals correctly
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "cluster.js"]

tini correctly handles SIGTERM forwarding and zombie reaping without adding significant overhead.

Entrypoint Scripts

For containers that need setup before the main process starts (e.g. running migrations, checking environment variables), use an entrypoint script:

FROM node:20-alpine
WORKDIR /app
COPY . .
RUN npm ci --omit=dev

COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

ENTRYPOINT ["/entrypoint.sh"]
CMD ["node", "server.js"]
#!/bin/sh
# entrypoint.sh
set -e

# Run migrations before starting the app
echo "Running database migrations..."
node migrate.js

# Then exec the CMD (replacing this shell process)
exec "$@"

The exec "$@" at the end is critical - it replaces the shell with the CMD process, ensuring it becomes PID 1 and receives signals properly.

Quick Reference

ScenarioUse
Simple app with one start commandCMD ["node", "server.js"]
Container as a CLI toolENTRYPOINT ["mytool"] + optional CMD ["--help"]
App with multiple subcommandsENTRYPOINT ["python", "manage.py"] + CMD ["runserver"]
Pre-start setup (migrations, checks)Entrypoint shell script + exec "$@"
Needs graceful shutdownExec form (not shell form)

Summary

  • Exec form (["node", "server.js"]) is preferred - your process gets PID 1 and receives signals correctly
  • Shell form (node server.js) wraps in /bin/sh -c - your process won't receive docker stop signals cleanly
  • CMD is a replaceable default - overridden by arguments after the image name in docker run
  • ENTRYPOINT is a fixed executable - docker run arguments are appended to it
  • Combine both: ENTRYPOINT for the executable, CMD for overridable default arguments
  • Only the last CMD or ENTRYPOINT in a Dockerfile is used
PreviousPrev
Next

Continue Learning

FROM

The FROM instruction starts every Dockerfile. Learn how to choose the right base image, use variants wisely, and unlock multi-stage builds.

8 min·Easy

COPY & ADD

Copy files into your image correctly. Learn the difference between COPY and ADD, how to set ownership, use .dockerignore, and avoid common mistakes.

7 min·Easy

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

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

CMD & ENTRYPOINTTwo Forms: Shell vs Exec`CMD` - Overridable Default`ENTRYPOINT` - Fixed ExecutableCMD + ENTRYPOINT TogetherOnly One CMD / ENTRYPOINT Is UsedSignal Handling and PID 1Entrypoint ScriptsQuick ReferenceSummary