How to create a Docker image from a running container?
docker commit lets you turn a running container into an image — a snapshot of whatever state the container is in. Tempting for shortcuts, dangerous for production. Knowing when it is the right tool is mostly about knowing when it is the wrong one.
Theory
TL;DR
docker commit <container> <image:tag>saves the container's current state as a new image.- Captures the writable layer + the underlying image layers.
- Optional flags:
-mmessage,-aauthor,-capply Dockerfile-style changes (CMD, ENV, etc.). - Anti-pattern for production. No Dockerfile, no reproducibility, no review trail, no peer review.
- Useful for: debug snapshots, capturing exploratory state, salvaging a fixed-up container before recreating from a Dockerfile.
Quick example
$ docker run -it --name explore alpine:3.21 sh
/ # apk add --no-cache curl
/ # echo 'custom config' > /etc/myconfig
/ # exit
$ docker commit -m 'added curl + custom config' explore my-explorer:0.1
sha256:abc123...
$ docker run --rm my-explorer:0.1 sh -c 'which curl && cat /etc/myconfig'
/usr/bin/curl
custom configThe interactive session's changes are now baked into a new image.
Syntax
docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
# Common options
-m "message" # commit message (visible in docker history)
-a "author" # author name
-p / --pause=false # by default, container is paused during commit; --pause=false skips pausing
-c "INSTRUCTION" # apply a Dockerfile instruction (CMD, ENV, EXPOSE, USER, WORKDIR, LABEL)Why it is anti-pattern for production
$ docker commit web myorg/web:1.0What the resulting image is, exactly:
- A snapshot of whatever the container had at this moment.
- Includes anything that was in the writable layer — installed packages, config tweaks, accidental files.
- No record of how to recreate it.
Problems:
- Not reproducible. If you lose the image, you cannot rebuild from source.
- No code review. A Dockerfile change goes through PR; a
docker commitdoes not. - Hidden side effects. Whatever the container did at runtime (logs, scratch files) lands in the image.
- Image bloat. Anonymous layers, no caching benefits, no
.dockerignore. - Drift. The image is whatever the human typed in the container, not a versioned recipe.
In production: write a Dockerfile, commit it to git, build via CI. Always.
Legitimate uses
Debug snapshots
A prod container is misbehaving. You want a frozen copy to investigate later without keeping the live container around:
$ docker commit -m 'snapshot before restart' broken-prod debug:broken-2026-04-30
$ docker push myreg/debug:broken-2026-04-30 # share with the teamNow you can docker run the snapshot in a sandbox to poke at it.
Capturing exploratory work
You are figuring out what packages an image needs. Iterate inside a container, then commit when you have it working — and use that to write the Dockerfile properly:
# Exploration
$ docker run -it --name explore alpine sh
/ # apk add --no-cache curl jq postgresql-client
/ # exit
# Capture state
$ docker commit explore exploration:scratch
# Now write the Dockerfile from what you learned:
# FROM alpine
# RUN apk add --no-cache curl jq postgresql-clientThe commit is a stepping stone, not the final artifact.
Disaster recovery
Prod container has a manual fix that has not been Dockerfile-ized yet, and the container is about to be replaced. Commit it to preserve the state until the Dockerfile is updated:
$ docker commit prod-fix saved:before-redeploy
# Open Dockerfile PR with the actual fix
# Once merged: deploy from new Dockerfile, retire 'saved:before-redeploy'Apply Dockerfile-style changes via -c
docker commit -c 'CMD ["/bin/bash"]' -c 'ENV PATH=/app/bin:$PATH' explore my-tweaked:0.1-c accepts a subset of Dockerfile instructions: CMD, ENTRYPOINT, ENV, EXPOSE, LABEL, USER, VOLUME, WORKDIR. Useful for adjusting metadata without entering a shell.
Common mistakes
Treating docker commit as a deploy mechanism
# WRONG: prod images via commit
$ docker exec api npm install some-package
$ docker commit api myreg/api:1.1
$ docker push myreg/api:1.1Six months later: nobody knows what is in myreg/api:1.1. The Dockerfile says one thing, the image is another. Use Dockerfile + CI build + push instead.
Forgetting that pause is default
docker commit pauses the container by default to ensure consistency. For long commits or high-throughput services, that pause is visible. Use --pause=false if you can tolerate inconsistency.
Committing a container with secrets in env
$ docker run -e DB_PASSWORD=hunter2 myapp
$ docker commit myapp leaked:image
# `leaked:image` now has DB_PASSWORD=hunter2 in its config
$ docker inspect leaked:image --format '{{json .Config.Env}}' | jq
["DB_PASSWORD=hunter2", ...]Committing bakes runtime env into the image. Push that and you have leaked the secret to anyone who can pull.
Treating commit-images as immutable history
Unlike Dockerfile-built images, you cannot easily diff two docker commit images. Reproducibility loss compounds: image A → tweak → commit → image B → tweak → commit → image C. Three weeks later, nobody knows what is in any of them.
Real-world usage
- Hotfix snapshots: prod container has a manual fix; commit before nuking it; later turn the fix into a Dockerfile change.
- Debug images for the support team: commit a problem container, push to a private registry, share with whoever needs to investigate.
- Onboarding playgrounds: commit a customized dev container with the team's tools so new devs
docker runonce and have everything. - Migration freeze: during a multi-step migration where the running container is the source of truth; commit at milestones for rollback options.
Follow-up questions
Q: What is the difference between docker commit and docker save?
A: commit creates a NEW image from a container. save exports an existing image to a tarball. Different use cases entirely.
Q: How do I see the changes a commit captured?
A: docker diff <container> BEFORE you commit shows what changed in the writable layer. After commit, the new image's docker history lists it as one big layer with your message.
Q: Can I commit a stopped container?
A: Yes. The writable layer is still there until docker rm.
Q: Will the new image inherit the original container's command, env, etc.?
A: Yes — the image config inherits from the original image. Use -c flags during commit to override specific parts (CMD, ENV, etc.).
Q: (Senior) When is docker commit actively dangerous?
A: When it becomes the deployment mechanism. Teams that commit-tweak-commit lose Dockerfile discipline, fail audits, cannot reproduce builds, and accumulate "works on the server" magic. The first sign: the source repo's Dockerfile no longer reflects the prod image. Treat docker commit as exclusively a debugging tool — never as a build step.
Examples
Snapshot a misbehaving prod container for offline analysis
# On prod
$ docker commit -m 'OOM-killing every 5 min - 2026-04-30' \
api debug-snapshots:api-oom-2026-04-30
$ docker tag debug-snapshots:api-oom-2026-04-30 myreg/debug:api-oom-2026-04-30
$ docker push myreg/debug:api-oom-2026-04-30
# In dev
$ docker pull myreg/debug:api-oom-2026-04-30
$ docker run -it --rm --entrypoint sh myreg/debug:api-oom-2026-04-30
# Now poke around in the same state prod was in.Iterate, commit, write Dockerfile from learnings
$ docker run -it --name lab python:3.13-slim bash
root@lab:/# pip install --no-cache-dir requests pyyaml psycopg2-binary
root@lab:/# echo 'export FOO=bar' >> /root/.bashrc
root@lab:/# exit
$ docker commit lab my-lab:0.1
# Wrote the equivalent Dockerfile based on what worked:
# FROM python:3.13-slim
# RUN pip install --no-cache-dir requests pyyaml psycopg2-binary
# ENV FOO=barUse the commit to capture state during exploration; then translate to a Dockerfile for production.
Apply config changes via -c
# Existing image, add labels and change default CMD
$ docker commit \
-c 'LABEL maintainer="team@example.com"' \
-c 'CMD ["/bin/bash"]' \
-c 'ENV TZ=UTC' \
explore tweaked:0.1Cheaper than rebuilding when you just want to adjust metadata on an existing image's snapshot.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.
Comments
No comments yet