Suggest an editImprove this articleRefine the answer for “How to create a Docker image from a running container?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**`docker commit`** snapshots a running (or stopped) container into a new image. Useful for debugging snapshots and exploratory work — but **not for production**, because the image is opaque (no Dockerfile, no reproducible recipe). ```bash docker commit -m "after manual fix" -a "alice" web myorg/web:hotfix docker images # myorg/web hotfix ... moments ago ``` **Key:** for debugging or capturing a state, fine. For production images, write a Dockerfile. A Dockerfile is the only reproducible, reviewable, version-controlled way to ship images.Shown above the full answer for quick recall.Answer (EN)Image**`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: `-m` message, `-a` author, `-c` apply 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 ```bash $ 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 config ``` The interactive session's changes are now baked into a new image. ### Syntax ```bash 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 ```bash $ docker commit web myorg/web:1.0 ``` What 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 commit` does 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: ```bash $ docker commit -m 'snapshot before restart' broken-prod debug:broken-2026-04-30 $ docker push myreg/debug:broken-2026-04-30 # share with the team ``` Now 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: ```bash # 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-client ``` The 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: ```bash $ 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` ```bash 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** ```bash # WRONG: prod images via commit $ docker exec api npm install some-package $ docker commit api myreg/api:1.1 $ docker push myreg/api:1.1 ``` Six 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** ```bash $ 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 run` once 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 ```bash # 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 ```bash $ 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=bar ``` Use the commit to capture state during exploration; then translate to a Dockerfile for production. ### Apply config changes via `-c` ```bash # 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.1 ``` Cheaper than rebuilding when you just want to adjust metadata on an existing image's snapshot.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.