Creating a volume is only the start. Real Docker work quickly becomes operational: you need to list volumes, inspect where data lives, remove abandoned storage, estimate disk usage, and sometimes back up or restore data before a migration. That is what volume management is about.
This lesson focuses on the Docker volume commands you use day to day. If named volumes give you persistence, volume management gives you control.
docker volume lsThe first command to know is:
docker volume lsThis lists all volumes Docker knows about on the current host.
Example output:
DRIVER VOLUME NAME
local pgdata
local uploads
local build-cacheYou use docker volume ls to:
In busy development machines, the list often grows faster than expected.
Docker also supports filter flags with docker volume ls.
docker volume ls --filter dangling=trueThat helps narrow the list to volumes matching a condition, such as unused volumes.
Filters are useful when cleaning up large systems because they reduce the chance of deleting something important by mistake.
docker volume inspectTo see details about a specific volume:
docker volume inspect pgdataThe output is JSON and includes fields like:
NameDriverMountpointCreatedAtLabelsScopeExample structure:
[
{
"Name": "pgdata",
"Driver": "local",
"Mountpoint": "/var/lib/docker/volumes/pgdata/_data",
"CreatedAt": "2026-07-12T12:00:00Z",
"Labels": {},
"Scope": "local"
}
]Name tells you which volume you are looking at.
Driver tells you how Docker manages it. For most local systems, this is local.
Mountpoint shows where the actual data is stored on the Docker host.
Scope is usually local, meaning the volume belongs to the current Docker host.
If you ever need to answer âWhere is my data?â or âWhich driver is this using?â, docker volume inspect is the right command.
docker volume rmTo delete one or more specific volumes:
docker volume rm pgdataOr:
docker volume rm old-cache temp-dataThis permanently removes the volume and its contents.
docker volume rm sometimes failsDocker does not remove a volume that is still in use by a container.
You may see an error like:
Error response from daemon: remove pgdata: volume is in useThat happens when a running or stopped container still references the volume.
Before removing a volume:
The refusal to delete in-use volumes is a useful safety barrier, not an annoyance.
docker volume pruneOver time, unused volumes accumulate. To remove all volumes that are not currently used by any container:
docker volume pruneDocker will ask for confirmation. To skip the prompt:
docker volume prune -fdocker volume prune removes unused volumes, not every volume on the system.
That sounds safe, but remember: unused does not mean unimportant. A volume can be detached from containers and still hold valuable data you plan to reuse later.
Treat docker volume prune as a cleanup tool, not as a casual command.
Volumes are persistent, but persistence is not backup. If the disk fails or a volume is deleted, the data is gone unless you have copied it elsewhere.
A classic Docker backup pattern uses a temporary container to read the volume and create an archive on the host.
docker run --rm -v myvolume:/data -v $(pwd):/backup alpine tar czf /backup/myvolume-backup.tar.gz -C /data .--rm removes the temporary helper container after it exits-v myvolume:/data mounts the Docker volume you want to back up-v $(pwd):/backup mounts your current host directory into the helper containeralpine provides a lightweight Linux imagetar czf /backup/myvolume-backup.tar.gz -C /data . creates a compressed archive from the volume contentsAfter the command finishes, myvolume-backup.tar.gz exists in your current host directory.
Docker volumes are managed by Docker, so you do not always want to manipulate their host storage paths directly. Using a helper container lets Docker handle the mount while you copy the data in a portable way.
Restoring follows the same helper-container idea in reverse.
First, make sure the target volume exists:
docker volume create myvolumeThen restore the archive into it:
docker run --rm -v myvolume:/data -v $(pwd):/backup alpine tar xzf /backup/myvolume-backup.tar.gz -C /data/dataNow a container that uses myvolume can read the restored data.
The same tar-based approach is useful beyond disaster recovery. It also helps with migration tasks such as:
For example, you might:
This is one of the simplest ways to move Docker-managed volume data between machines.
docker system dfDocker volume usage contributes to host disk consumption, but it is not always obvious how much. A helpful overview command is:
docker system dfThis shows disk usage for Docker objects such as:
Example conceptual output:
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 12 4 3.1GB 1.8GB
Containers 5 2 120MB 40MB
Local Volumes 8 3 2.6GB 1.9GB
Build Cache 15 0 900MB 900MBdocker system df mattersIf your machine suddenly runs low on disk space, volumes are often part of the story. This command helps you identify whether local volumes are a major storage consumer before you start cleaning up.
Here is a safe volume management flow for a development machine:
docker volume lsdocker volume inspect pgdatadocker system dfdocker run --rm -v pgdata:/data -v $(pwd):/backup alpine tar czf /backup/pgdata-backup.tar.gz -C /data .docker volume rm old-cachedocker volume pruneThis workflow is safer than jumping straight to mass deletion.
You try:
docker volume rm pgdataDocker says the volume is in use.
Interpretation: at least one container still references it. Find the container, decide whether it should keep the data, then stop or remove the container if deletion is truly intended.
Run:
docker system dfIf local volumes are large, inspect your active and dangling volumes. Old database experiments and Compose projects are common causes.
Back up the volume first. Even if the upgrade is expected to be safe, a quick archive is good insurance.
Back up, transfer the archive, create a volume on the destination, and restore.
Names like postgres-prod-data are easier to manage than vague names like data1.
If the data matters, archive it first.
A few seconds of inspection can prevent serious data loss.
Pruning is helpful, but only after you understand what is unused.
Docker storage grows quietly. docker system df gives you a quick reality check.
Confusing persistence with backup A volume surviving container deletion does not protect against host failure or accidental removal.
Running prune without review Unused data may still be valuable.
Deleting a volume just because the container is gone The whole point of a volume is to outlive the container.
Ignoring disk usage until the host is full Storage problems are easier to prevent than to clean up under pressure.
Not testing restore procedures A backup only proves its value when it can be restored successfully.
Volume management is what turns Docker storage from a mystery into an operational skill. Learn to list volumes, inspect them, remove them carefully, prune only with intent, and back them up before risky changes. If you can manage Docker volumes confidently, you are much better prepared to run stateful containers in both development and production.
<MCQExercise exercise={{ id: 'docker-volume-management-q1', title: 'Question 1: Listing Volumes', description: 'Which command lists all Docker volumes on the current host?', options: [ { id: 'a', text: 'docker volume ls' }, { id: 'b', text: 'docker volumes show' }, { id: 'c', text: 'docker storage ls' }, { id: 'd', text: 'docker inspect volumes' }, ], correctId: 'a', explanation: 'docker volume ls is the standard command for listing Docker volumes and quickly seeing their names and drivers.', wrongExplanations: { b: 'docker volumes show is not a valid Docker command.', c: 'docker storage ls is not the command used for Docker volumes.', d: 'docker inspect volumes is not the standard listing command.', }, }} />
<MCQExercise exercise={{ id: 'docker-volume-management-q2', title: 'Question 2: Removal Safety', description: 'Why might docker volume rm myvolume fail?', options: [ { id: 'a', text: 'Because Docker volumes can never be removed manually' }, { id: 'b', text: 'Because the volume is still in use by a container' }, { id: 'c', text: 'Because only bind mounts can be removed' }, { id: 'd', text: 'Because docker volume rm works only on Windows hosts' }, ], correctId: 'b', explanation: 'Docker protects in-use volumes from removal. If a container still references the volume, Docker refuses to delete it until that relationship is removed.', wrongExplanations: { a: 'Volumes can be removed manually when they are not in use.', c: 'Bind mounts are host paths, not Docker volumes. The command is specifically for volumes.', d: 'docker volume rm is a normal cross-platform Docker command.', }, }} />
<MCQExercise exercise={{ id: 'docker-volume-management-q3', title: 'Question 3: Backup Pattern', description: 'Why is a temporary helper container commonly used to back up a Docker volume?', options: [ { id: 'a', text: 'Because Docker only allows backups from inside Kubernetes' }, { id: 'b', text: 'Because the helper container can mount the volume and archive its contents into a file on the host' }, { id: 'c', text: 'Because helper containers convert volumes into images automatically' }, { id: 'd', text: 'Because tar works only inside alpine when Docker is installed' }, ], correctId: 'b', explanation: 'A helper container provides a simple, portable way to mount the volume and create or extract an archive without manually navigating Docker internal storage paths.', wrongExplanations: { a: 'Kubernetes is unrelated to basic Docker volume backup workflows.', c: 'Backup helpers archive data. They do not transform volumes into images.', d: 'Alpine is a convenient lightweight image, not the only place tar can run.', }, }} />
Because volumes are excluded by default, deliberately. They usually hold the data you least want to lose. docker system prune removes stopped containers, unused networks, dangling images and build cache, but leaves volumes alone unless you add --volumes. Use docker volume prune to remove volumes no container references, and check docker volume ls first, since a volume that looks unused may simply belong to a container you stopped.
docker ps -a --filter volume=<name> lists containers referencing it, including stopped ones. docker volume inspect does not show consumers, which is a common source of confusion. This matters before pruning, since a volume attached only to a stopped container still counts as in use and will not be removed, while one whose container was deleted becomes eligible immediately.
Archive it through a temporary container and transfer the archive: docker run --rm -v myvolume:/data -v $(pwd):/backup alpine tar czf /backup/vol.tar.gz -C /data ., then reverse the process on the destination after creating an empty volume there. There is no built-in volume migration command. For databases, use the engine's own dump and restore rather than a filesystem copy, which can capture an inconsistent state if the database is running.
Start with docker system df -v, which breaks down usage per volume and shows which are unused. Then docker volume prune removes those with no container attached. Be deliberate. Unlike image pruning, this deletes data permanently and there is no recovery. Anonymous volumes created implicitly by images with a VOLUME instruction are a common hidden consumer, since each docker run creates a fresh one that nothing subsequently references.
When an image declares VOLUME /data and you run it without mounting anything there, Docker creates an anonymous volume with a random hexadecimal name. Every docker run creates another, so repeatedly starting such an image, database images commonly do this, leaves a trail of orphaned volumes holding real data. Mount a named volume explicitly to avoid it, add --rm which removes anonymous volumes on exit, and run docker volume prune periodically to clear the ones already accumulated.
2d:["$","$L2e",null,{"tutorial":{"slug":"docker-containers","title":"Docker Containers","seoTitle":"Docker Containers: Lifecycle, Ports and Debugging","description":"Learn how Docker containers work, from docker run and the container lifecycle to port publishing, restart policies and why a container exits immediately.","content":"$2f","difficulty":"Easy","duration":"15 min","keywords":["docker containers","container lifecycle","docker run","container management","docker networking","docker container exits immediately","docker run vs start","docker port mapping","docker restart policy","docker exec into running container","docker container logs","remove all docker containers","docker stop vs kill"],"publishedDate":"2026-02-04","updatedDate":"2026-09-16","order":19,"tableOfContents":[{"id":"the-lifecycle","title":"The lifecycle","level":2},{"id":"why-your-container-exits-immediately","title":"Why your container exits immediately","level":2},{"id":"publishing-ports","title":"Publishing ports","level":2},{"id":"environment-and-configuration","title":"Environment and configuration","level":2},{"id":"restart-policies","title":"Restart policies","level":2},{"id":"resource-limits","title":"Resource limits","level":2},{"id":"working-with-a-running-container","title":"Working with a running container","level":2},{"id":"stop-vs-kill","title":"stop vs kill","level":2},{"id":"cleaning-up","title":"Cleaning up","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"Why does my Docker container exit immediately?","answer":"Because its main process finished. A container runs exactly as long as PID 1, so anything that completes, or daemonises itself into the background, ends the container. Check docker ps -a for the exit code and docker logs <container> for output. Exit 0 means a clean finish, so the command simply ran and stopped; exit 127 means command not found, usually a typo or a binary missing from the image. For servers, keep the process in the foreground, such as nginx -g "daemon off;"."},{"question":"What is the difference between docker run and docker start?","answer":"docker run creates a new container from an image and starts it, so running the same command three times produces three containers. docker start restarts an existing, stopped container by name or ID, preserving its writable layer and configuration. If docker ps -a is filling up with stopped containers, that is run being used where start was intended, or a missing --rm flag on one-off commands."},{"question":"How does Docker port mapping work?","answer":"-p hostPort:containerPort. Host on the left, container on the right. So -p 8080:80 makes a container listening on 80 reachable at localhost:8080. Reversing them is extremely common and produces a mapping that silently forwards to a port nothing is listening on. Note that EXPOSE in a Dockerfile publishes nothing; it only documents the port. Use -p 127.0.0.1:8080:80 to bind to localhost only, since a plain -p binds all interfaces and Docker's own iptables rules can bypass a host firewall."},{"question":"How do I get a shell inside a Docker container?","answer":"docker exec -it <container> sh for a running container, using bash if the image has it. Many slim images do not. If the container has already exited, exec will not work; start a new one with an overridden command instead: docker run --rm -it --entrypoint sh <image>. That is the standard way to explore an image whose normal command exits immediately or crashes on start."},{"question":"Which restart policy should I use?","answer":"unless-stopped for most long-running services. It restarts the container after a crash or a Docker daemon restart, but respects a deliberate docker stop, whereas always will bring back a container you intentionally stopped the next time the daemon starts, which is rarely wanted. Use on-failure for batch work that should retry on error but not after a successful run, and leave the default no for one-off commands."},{"question":"How do I remove all stopped Docker containers?","answer":"docker container prune removes every stopped container in one command and reports how much space was reclaimed. To remove absolutely everything including running containers, docker ps -aq | xargs docker rm -f works but should be used carefully. The better habit is prevention: add --rm to any one-off docker run so the container deletes itself on exit, which stops the buildup entirely."}]},"allTutorials":[{"slug":"introduction-to-docker","title":"Introduction to Docker","seoTitle":"What Is Docker? A Beginner's Introduction to Containers","description":"Learn what Docker is and why developers use it. Understand images vs containers, how Docker differs from a virtual machine, the basic workflow and its limits.","content":"$30","difficulty":"Easy","duration":"14 min","keywords":["what is docker","docker for beginners","docker vs virtual machine","docker image vs container","why use docker","containerization","docker architecture","docker tutorial","docker basics","learn docker","containerization guide","docker introduction","docker containers explained","docker vs virtual machines","docker commands cheat sheet","devops tools","container orchestration","docker installation","docker compose","microservices docker"],"publishedDate":"2026-02-04","updatedDate":"2026-09-15","order":1,"tableOfContents":[{"id":"the-problem","title":"The problem","level":2},{"id":"images-and-containers","title":"Images and containers","level":2},{"id":"how-this-differs-from-a-virtual-machine","title":"How this differs from a virtual machine","level":2},{"id":"the-workflow","title":"The workflow","level":2},{"id":"the-pieces","title":"The pieces","level":2},{"id":"containers-are-disposable","title":"Containers are disposable","level":2},{"id":"where-docker-is-not-the-answer","title":"Where Docker is not the answer","level":2},{"id":"where-to-go-next","title":"Where to go next","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What is Docker used for?","answer":"Packaging an application with its runtime, libraries and configuration into a single image that runs identically anywhere Docker runs. It eliminates the "works on my machine" class of problem, since the environment ships with the application rather than being recreated at each destination. In practice it is used for local development environments, CI build and test isolation, and as the deployment unit for anything running on Kubernetes, ECS or similar platforms."},{"question":"What is the difference between a Docker image and a container?","answer":"An image is a read-only template built from a Dockerfile; a container is a running instance created from that image with its own writable layer. The relationship is like a class and an object. One image can produce any number of containers, all sharing the same underlying layers and differing only in their writable layer and running state. Images are built and pulled; containers are run, stopped and removed."},{"question":"How is Docker different from a virtual machine?","answer":"A VM virtualises hardware and runs a complete guest operating system with its own kernel, typically taking gigabytes and tens of seconds to boot. A container shares the host kernel and isolates only the process, so it is measured in megabytes and starts in milliseconds. That makes containers far denser and faster, at the cost of weaker isolation, and it means a container cannot run a different kernel, which is why Linux containers on macOS or Windows run inside a small managed VM."},{"question":"Do I need to learn Docker before Kubernetes?","answer":"Yes. Kubernetes orchestrates containers and assumes you already understand images, containers, ports, volumes and environment variables. A large share of Kubernetes problems turn out to be container problems in disguise: a wrong entrypoint, a missing environment variable, a process writing to a path that is not persisted. Being comfortable with docker build and docker run makes the Kubernetes learning curve considerably shorter."},{"question":"Why does my data disappear when a container restarts?","answer":"Because a container's writable layer is destroyed when the container is removed, and it is not preserved across docker rm and a fresh docker run. Anything that must survive belongs in a volume, mounted into the container, or in an external service such as a database. This catches nearly everyone once, and it is the practical expression of the principle that containers are disposable. Configuration comes from environment variables and data lives outside."},{"question":"Is Docker always the right choice?","answer":"No. A single simple application running fine on one server gains little from containerisation and pays the cost of another layer to understand and maintain. Desktop GUI applications are awkward in containers. Workloads needing a specific kernel or kernel modules cannot use them, since containers share the host kernel. And genuinely untrusted multi-tenant code deserves a stronger boundary than a container provides. A VM or a microVM runtime such as Firecracker."}]},{"slug":"why-use-docker","title":"Why Use Docker?","seoTitle":"Why Use Docker? Benefits and Real-World Use Cases","description":"Find out why teams use Docker: consistent environments, faster deployment, easier scaling and microservices support, with real-world use cases.","content":"$31","difficulty":"Easy","duration":"","keywords":["why docker?","docker benefits","docker use cases","container advantages","docker microservices","docker ci/cd"],"publishedDate":"2026-02-04","updatedDate":"2026-02-04","order":2,"tableOfContents":[{"id":"why-use-docker","title":"Why Use Docker?","level":2},{"id":"key-benefits","title":"Key Benefits","level":2},{"id":"consistency---it-works-the-same-everywhere","title":"Consistency - "It works the same, everywhere"","level":3},{"id":"isolation---apps-mind-their-own-business","title":"Isolation - "Apps mind their own business"","level":3},{"id":"portability---run-it-anywhere","title":"Portability - "Run it anywhere"","level":3},{"id":"efficiency---lightweight-and-fast","title":"Efficiency - "Lightweight and fast"","level":3},{"id":"speed---start-in-seconds-not-minutes","title":"Speed - "Start in seconds, not minutes"","level":3},{"id":"scalability---grow-without-the-pain","title":"Scalability - "Grow without the pain"","level":3},{"id":"real-world-use-cases","title":"Real-World Use Cases","level":2},{"id":"setting-up-a-development-environment","title":"Setting Up a Development Environment","level":3},{"id":"microservices-building-apps-in-pieces","title":"Microservices (Building Apps in Pieces)","level":3},{"id":"cicd-automated-testing-deployment","title":"CI/CD: Automated Testing & Deployment","level":3},{"id":"modernizing-old-apps","title":"Modernizing Old Apps","level":3},{"id":"running-multiple-customer-environments","title":"Running Multiple Customer Environments","level":3},{"id":"the-big-picture","title":"The Big Picture","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What problem does Docker actually solve?","answer":"Environment inconsistency. An application that works on a developer's laptop and fails in staging almost always differs in something environmental: a language version, a missing system library, a config file in a different place, a locale setting. Docker ships the environment with the application, so the thing that runs in production is byte-identical to the thing that was tested. The secondary benefits, fast startup, high density, a standard deployment unit, follow from the same packaging."},{"question":"Is Docker worth it for a small project?","answer":"For local development and CI, usually yes. A reproducible environment costs an afternoon to set up and removes a recurring class of problem, especially when a second person joins. For deployment it is more of a judgement call: a single application running happily on one server gains little from containerisation and pays the cost of another layer to understand. The usual tipping point is a second developer, a second environment, or a CI pipeline."},{"question":"Does Docker make my application faster?","answer":"No, and it is worth being clear about that. Containers add negligible runtime overhead compared with running the process directly, but they do not speed anything up either. What gets faster is everything around the application: environment setup, deployment, scaling and rollback. On macOS and Windows, filesystem access from a container is actually slower than native, because Linux containers run inside a managed VM."},{"question":"What is the difference between Docker and Kubernetes?","answer":"Docker builds and runs containers on a single machine. Kubernetes orchestrates containers across many machines. Scheduling, health checking, load balancing, rolling updates and scaling. They operate at different levels and are commonly used together: Docker to build the image, Kubernetes to run it in production. Kubernetes stopped using Docker as its runtime in version 1.24, but Docker-built images run on it unchanged because both follow the OCI standard."},{"question":"Do containers replace virtual machines?","answer":"Not entirely. They solve overlapping problems with different trade-offs. Containers share the host kernel, so they start in milliseconds and pack densely, but isolation is weaker and you cannot run a different kernel. VMs virtualise hardware, giving stronger isolation at the cost of size and startup time. In practice most cloud deployments run containers inside VMs, getting container density with VM-level isolation between tenants."},{"question":"What are the downsides of using Docker?","answer":"Real ones worth weighing. There is a genuine learning curve covering images, layers, networking, volumes and orchestration. Persistent data needs deliberate handling, since anything written inside a container is lost when it is replaced. Debugging is less direct than on a plain host. Isolation is weaker than a VM's, so genuinely untrusted code deserves a stronger boundary. And on macOS and Windows, the hidden Linux VM makes file sharing noticeably slow for large projects."}]},{"slug":"docker-vs-virtual-machines","title":"Docker vs Virtual Machines","seoTitle":"Docker vs Virtual Machines: Key Differences Explained","description":"Compare Docker containers and virtual machines on architecture, startup time, isolation and security, and learn when to choose one or use both together.","content":"$32","difficulty":"Easy","duration":"13 min","keywords":["docker vs virtual machines","containers vs vms","container vs vm difference","docker vm comparison","are containers more secure than vms","container isolation","hypervisor vs container runtime","docker on mac windows vm","when to use vm instead of container"],"publishedDate":"2026-02-04","updatedDate":"2026-09-16","order":3,"tableOfContents":[{"id":"the-architectural-difference","title":"The architectural difference","level":2},{"id":"the-practical-comparison","title":"The practical comparison","level":2},{"id":"the-two-limits-that-actually-matter","title":"The two limits that actually matter","level":2},{"id":"are-containers-less-secure","title":"Are containers less secure?","level":2},{"id":"when-to-use-which","title":"When to use which","level":2},{"id":"in-practice-you-use-both","title":"In practice you use both","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What is the main difference between Docker and a virtual machine?","answer":"What gets virtualised. A VM virtualises hardware. A hypervisor presents virtual CPUs and disks, and each VM boots a complete guest operating system with its own kernel. A container virtualises the operating system. It is a process on the host isolated by kernel namespaces and cgroups, sharing the host kernel. That is why containers start in milliseconds and measure in megabytes while VMs take tens of seconds and measure in gigabytes."},{"question":"Are containers more secure than virtual machines?","answer":"No. VMs provide stronger isolation. All containers on a host share one kernel, so a kernel vulnerability or a container escape can reach the host and other containers, whereas a VM escape must also get past the hypervisor, a far smaller and more hardened boundary. For your own code the difference rarely matters in practice. For genuinely untrusted multi-tenant code it does, which is why such platforms use VMs or microVM runtimes like Firecracker and gVisor."},{"question":"Can Docker run Windows containers on Linux, or Linux containers on Windows?","answer":"Not natively, because containers share the host kernel. Windows containers need a Windows kernel and Linux containers need a Linux one. Docker Desktop makes Linux containers work on macOS and Windows by running a lightweight Linux VM behind the scenes, on Windows it typically uses WSL 2. Everything behaves transparently, but it explains why bind-mounted file access is noticeably slower on those platforms than on native Linux."},{"question":"Do containers replace virtual machines?","answer":"No, and in practice they are used together. Almost every cloud container platform runs containers inside VMs: an EKS or GKE node is a VM hosting many pods, and Fargate runs each task in its own microVM. You get container packaging and density within a VM boundary that separates tenants. Containers changed how applications are packaged and scheduled; VMs remain the unit of hardware-level isolation underneath."},{"question":"When should I use a VM instead of a container?","answer":"When you need a different operating system or kernel version than the host, when running genuinely untrusted third-party code, for legacy applications that expect a full operating system with init and multiple services, when compliance requires hardware-level isolation between workloads, and for kernel-level work such as drivers or custom modules. For everything else, stateless services, CI jobs, development environments, containers are lighter and faster."},{"question":"Why is Docker slow on macOS and Windows?","answer":"Because Linux containers cannot run natively there. Docker Desktop runs a Linux VM and your containers live inside it, so every bind-mounted file access crosses the boundary between the host filesystem and the VM. For a project with many small files, such as a large node_modules, this is the dominant cost. Mitigate it by bind-mounting only the source directory, keeping dependency directories in named volumes, and enabling VirtioFS in Docker Desktop's settings."}]},{"slug":"installing-docker","title":"Installing Docker","seoTitle":"How to Install Docker: Desktop, Engine and Verify It","description":"Choose between Docker Desktop and Docker Engine, check the requirements, verify the install with docker run hello-world, and fix the three most common failures.","content":"$33","difficulty":"Easy","duration":"20 min","keywords":["install docker","docker desktop","docker installation","setup docker","docker windows","docker mac","docker linux","docker configuration","docker desktop vs docker engine","docker desktop license","docker desktop free for commercial use","docker permission denied socket","verify docker installation","docker hello world","docker alternatives podman colima"],"publishedDate":"2026-02-04","updatedDate":"2026-09-16","order":4,"tableOfContents":[{"id":"docker-desktop-is-not-the-only-option","title":"Docker Desktop is not the only option","level":2},{"id":"the-licensing-question","title":"The licensing question","level":3},{"id":"requirements","title":"Requirements","level":2},{"id":"verify-the-installation","title":"Verify the installation","level":2},{"id":"the-three-failures-almost-everyone-hits","title":"The three failures almost everyone hits","level":2},{"id":"1-permission-denied-on-the-socket-linux","title":"1. "permission denied" on the socket (Linux)","level":3},{"id":"2-cannot-connect-to-the-docker-daemon","title":"2. "Cannot connect to the Docker daemon"","level":3},{"id":"3-virtualisation-disabled-windows","title":"3. Virtualisation disabled (Windows)","level":3},{"id":"after-it-works","title":"After it works","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"Is Docker Desktop free for commercial use?","answer":"Only below a size threshold. Docker Desktop requires a paid subscription for business use in organisations with more than 250 employees or more than $10 million in annual revenue; it stays free for personal use, education, non-commercial open source and smaller companies. Docker Engine, the daemon that builds and runs containers, is Apache 2.0 licensed and free in every case, which is why Linux servers and CI runners are unaffected. If the subscription is a blocker on developer machines, Podman, Colima and Rancher Desktop all expose a Docker-compatible interface."},{"question":"What is the difference between Docker Desktop and Docker Engine?","answer":"Docker Engine is the daemon plus CLI that does the actual work, and on Linux it runs containers directly against the host kernel. Docker Desktop is a packaged product that bundles that engine with a Linux virtual machine, a GUI, Compose, an optional Kubernetes cluster and automatic updates. On Windows and macOS the VM is not optional, containers are a Linux kernel feature, which is why Desktop or an equivalent is required there and merely convenient on Linux."},{"question":"How do I fix "permission denied" on /var/run/docker.sock?","answer":"Add your user to the docker group with sudo usermod -aG docker $USER, then run newgrp docker or log out and back in. The change will not affect an already-open shell, because supplementary groups are read at login, which is why it often appears not to have worked. Understand the trade: anyone in the docker group can mount the host filesystem into a privileged container, so it is equivalent to root access on that machine. On shared systems, rootless mode is the safer arrangement."},{"question":"How do I know Docker installed correctly?","answer":"Run three commands. docker --version proves the CLI is on your PATH. docker info proves the daemon is running and the CLI can reach it. docker run --rm hello-world proves the whole path works. Registry authentication, image pull, storage, container runtime and networking, and prints a confirmation message. If the first two succeed and the third fails, the problem is connectivity or registry access rather than the installation."},{"question":"Why does Docker say "Cannot connect to the Docker daemon"?","answer":"The CLI is installed and nothing is listening on the socket, which is a different problem from Docker being missing. On Linux, start the service with sudo systemctl enable --now docker. On Windows or macOS, Docker Desktop is not running or is still starting. The CLI ships with the same installer and works long before the VM has finished booting, so this appears often right after installing. Check the whale icon has stopped animating before retrying."},{"question":"Do I need WSL 2 to run Docker on Windows?","answer":"In practice yes. Docker Desktop's WSL 2 backend is the supported and far faster option; the older Hyper-V backend is deprecated. Run wsl --install then wsl --set-default-version 2, and make sure hardware virtualisation is enabled in the BIOS, systeminfo | findstr /C:"Virtualization" tells you whether Windows can see it. Containers then run inside a managed Linux distribution, with bind mounts noticeably faster when your project lives on the WSL filesystem rather than under /mnt/c."}]},{"slug":"installing-docker-windows","title":"Installing Docker on Windows","seoTitle":"How to Install Docker on Windows 10/11 With WSL 2","description":"Install Docker Desktop on Windows 10 or 11 with the WSL 2 backend. Covers prerequisites, settings, troubleshooting and filesystem choice for build speed.","content":"$34","difficulty":"Easy","duration":"20 min","keywords":["install docker windows","docker desktop windows","wsl 2 docker","docker windows installation","windows docker setup","docker desktop wsl 2 backend","enable virtualization windows docker","docker windows 11 home","wsl 2 slow file performance docker","docker desktop not starting windows","wsl update failed docker","docker windows containers vs linux containers"],"publishedDate":"2026-02-04","updatedDate":"2026-09-16","order":5,"tableOfContents":[{"id":"prerequisites","title":"Prerequisites","level":2},{"id":"windows-requirements","title":"Windows Requirements","level":3},{"id":"installing-docker-desktop-on-windows","title":"Installing Docker Desktop on Windows","level":2},{"id":"step-1-enable-wsl-2","title":"Step 1: Enable WSL 2","level":3},{"id":"step-2-download-docker-desktop","title":"Step 2: Download Docker Desktop","level":3},{"id":"step-3-install-docker-desktop","title":"Step 3: Install Docker Desktop","level":3},{"id":"step-4-start-docker-desktop","title":"Step 4: Start Docker Desktop","level":3},{"id":"step-5-verify-installation","title":"Step 5: Verify Installation","level":3},{"id":"configuring-docker-desktop-on-windows","title":"Configuring Docker Desktop on Windows","level":2},{"id":"resources-allocation","title":"Resources Allocation","level":3},{"id":"docker-desktop-settings","title":"Docker Desktop Settings","level":3},{"id":"troubleshooting-windows-installation","title":"Troubleshooting Windows Installation","level":2},{"id":"wsl-2-not-available","title":"WSL 2 Not Available","level":3},{"id":"virtualization-not-enabled","title":"Virtualization Not Enabled","level":3},{"id":"docker-service-wont-start","title":"Docker Service Won't Start","level":3},{"id":"cannot-pull-images","title":"Cannot Pull Images","level":3},{"id":"testing-your-installation","title":"Testing Your Installation","level":2},{"id":"1-check-docker-version","title":"1. Check Docker Version","level":3},{"id":"2-run-a-test-container","title":"2. Run a Test Container","level":3},{"id":"3-list-running-containers","title":"3. List Running Containers","level":3},{"id":"4-stop-and-remove-the-container","title":"4. Stop and Remove the Container","level":3},{"id":"windows-specific-tips","title":"Windows-Specific Tips","level":2},{"id":"using-wsl-2-with-docker","title":"Using WSL 2 with Docker","level":3},{"id":"file-performance","title":"File Performance","level":3},{"id":"powershell-vs-cmd","title":"PowerShell vs CMD","level":3},{"id":"the-filesystem-choice-that-decides-your-build-speed","title":"The filesystem choice that decides your build speed","level":2},{"id":"where-to-go-next","title":"Where to go next","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"Can I run Docker Desktop on Windows 10 or 11 Home?","answer":"Yes. The old requirement for Pro, Enterprise or Education applied to the Hyper-V backend, which needed a feature Home does not ship. The WSL 2 backend has no such dependency and is now the default and recommended option, so Home editions run Docker Desktop normally. You still need a 64-bit build 19041 or later, hardware virtualisation enabled in firmware, and WSL 2 installed."},{"question":"Why is Docker so slow on Windows?","answer":"Almost always because the project lives on the Windows filesystem and is reached from WSL 2 through /mnt/c. That path crosses a translation layer that handles large sequential reads reasonably and the thousands of tiny file operations a build produces very poorly. Clone the repository inside the WSL distribution instead, cd ~ && git clone â¦, and open it with VS Code's Remote-WSL extension. Add a .dockerignore excluding node_modules and .git so the build context stays small either way."},{"question":"How do I fix "WSL 2 installation is incomplete" or a failed WSL update?","answer":"Run wsl --install from an elevated PowerShell, then wsl --update and wsl --set-default-version 2, and reboot. If it still fails, the usual cause is virtualisation being disabled in firmware, systeminfo | findstr /C:"Virtualization" reporting No confirms it, and the setting is Intel VT-x or AMD-V, often labelled SVM Mode on AMD boards. Corporate machines sometimes also have the Virtual Machine Platform Windows feature disabled by policy, which wsl --install cannot override."},{"question":"Why does Docker Desktop say the daemon is not running right after I installed it?","answer":"Because the CLI and the engine arrive together but start apart. docker.exe is on your PATH as soon as the installer finishes, while the Linux VM behind it takes a while to boot, so a command typed immediately gets "Cannot connect to the Docker daemon". Wait for the whale icon in the system tray to stop animating. If it never settles, check Docker Desktop's own diagnostics: the usual culprits are WSL 2 not fully installed or virtualisation switched off."},{"question":"How much memory does Docker Desktop use on Windows, and can I limit it?","answer":"The WSL 2 VM (vmmem in Task Manager) grows as containers demand memory and by default may claim a large share of your RAM. Cap it by creating %UserProfile%\.wslconfig with a [wsl2] section setting memory=6GB and processors=4, then run wsl --shutdown and restart Docker Desktop. Note WSL 2 is slow to hand memory back to Windows even when containers stop, which is why an explicit cap is worth setting rather than relying on it releasing on its own."},{"question":"What is the difference between Windows containers and Linux containers?","answer":"Linux containers are what essentially all tutorials, base images and production workloads mean by "containers", and on Windows they run inside the WSL 2 Linux VM. Windows containers are a separate mode that runs Windows processes against the Windows kernel, used for containerising .NET Framework and other Windows-only applications. You switch between modes from the Docker Desktop tray menu, and they cannot run at the same time. Unless you have specifically been asked for Windows containers, stay in Linux mode."}]},{"slug":"installing-docker-macos","title":"Installing Docker on macOS","seoTitle":"How to Install Docker on Mac (Intel and Apple Silicon)","description":"Install Docker Desktop on macOS for Intel or Apple Silicon (M1, M2, M3). Covers setup, resource settings, the Dashboard, verifying the install and common fixes.","content":"$35","difficulty":"Easy","duration":"15 min","keywords":["install docker mac","docker desktop mac","docker macos","docker apple silicon","docker m1 m2","docker intel mac","docker apple silicon exec format error","docker mac slow volume performance","virtiofs docker desktop","colima docker mac","docker buildx multi-arch mac","install docker mac homebrew","docker desktop mac memory usage"],"publishedDate":"2026-02-04","updatedDate":"2026-09-16","order":6,"tableOfContents":[{"id":"prerequisites","title":"Prerequisites","level":2},{"id":"macos-requirements","title":"macOS Requirements","level":3},{"id":"installing-docker-desktop-on-macos","title":"Installing Docker Desktop on macOS","level":2},{"id":"step-1-download-docker-desktop","title":"Step 1: Download Docker Desktop","level":3},{"id":"step-2-install-docker-desktop","title":"Step 2: Install Docker Desktop","level":3},{"id":"step-3-configure-docker-desktop","title":"Step 3: Configure Docker Desktop","level":3},{"id":"step-4-verify-installation","title":"Step 4: Verify Installation","level":3},{"id":"configuring-docker-desktop-on-macos","title":"Configuring Docker Desktop on macOS","level":2},{"id":"resources-allocation","title":"Resources Allocation","level":3},{"id":"docker-desktop-settings","title":"Docker Desktop Settings","level":3},{"id":"troubleshooting-macos-installation","title":"Troubleshooting macOS Installation","level":2},{"id":"permission-denied","title":"Permission Denied","level":3},{"id":"docker-not-starting","title":"Docker Not Starting","level":3},{"id":"slow-performance","title":"Slow Performance","level":3},{"id":"cannot-pull-images","title":"Cannot Pull Images","level":3},{"id":"testing-your-installation","title":"Testing Your Installation","level":2},{"id":"1-check-docker-version","title":"1. Check Docker Version","level":3},{"id":"2-view-system-information","title":"2. View System Information","level":3},{"id":"3-run-a-test-container","title":"3. Run a Test Container","level":3},{"id":"4-list-running-containers","title":"4. List Running Containers","level":3},{"id":"5-stop-and-remove-the-container","title":"5. Stop and Remove the Container","level":3},{"id":"macos-specific-features","title":"macOS-Specific Features","level":2},{"id":"apple-silicon-optimization","title":"Apple Silicon Optimization","level":3},{"id":"file-sharing-performance","title":"File Sharing Performance","level":3},{"id":"virtualization-framework","title":"Virtualization Framework","level":3},{"id":"docker-desktop-dashboard","title":"Docker Desktop Dashboard","level":2},{"id":"best-practices-for-macos","title":"Best Practices for macOS","level":2},{"id":"1-keep-docker-updated","title":"1. Keep Docker Updated","level":3},{"id":"2-allocate-resources-wisely","title":"2. Allocate Resources Wisely","level":3},{"id":"3-use-official-images","title":"3. Use Official Images","level":3},{"id":"4-configure-file-sharing","title":"4. Configure File Sharing","level":3},{"id":"apple-silicon-the-architecture-problem","title":"Apple Silicon: the architecture problem","level":2},{"id":"bind-mount-performance","title":"Bind mount performance","level":2},{"id":"if-you-would-rather-not-run-docker-desktop","title":"If you would rather not run Docker Desktop","level":2},{"id":"where-to-go-next","title":"Where to go next","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"Why does my Docker image built on an M1 or M2 Mac fail on the server?","answer":"Because Docker builds for the host architecture, and Apple Silicon is arm64 while most servers are amd64. The container starts and immediately exits with exec format error, which does not mention architecture at all. Check with docker image inspect <image> --format '{{.Architecture}}'. For images others will run, build multi-architecture: docker buildx build --platform linux/amd64,linux/arm64 -t <image> --push .. For a quick one-off, docker build --platform linux/amd64 works but runs under emulation and is slower."},{"question":"Why are Docker volumes so slow on macOS?","answer":"Every file operation on a bind-mounted host directory has to cross the boundary into the Linux VM, and workloads like nodemodules or a Rails app generate enormous numbers of small operations. Make sure VirtioFS is selected in Settings â General. It is the default now and much faster than the older gRPC-FUSE, but upgraded installations sometimes still use the old one. Then shadow dependency directories with an anonymous volume (- /app/nodemodules in Compose) so they stay inside the VM's own filesystem."},{"question":"Should I install Docker Desktop with Homebrew or the .dmg?","answer":"Either works and they install the same application. brew install --cask docker is easier to script and keeps Docker in the same update flow as your other tools, which is why it suits dotfiles and new-machine setups. The .dmg from Docker's site is more explicit about which build you are getting, which matters because Apple Silicon and Intel have separate downloads. Homebrew picks the right one for your machine automatically."},{"question":"What is the free alternative to Docker Desktop on Mac?","answer":"Colima is the most common: brew install colima docker docker-compose then colima start. It runs Docker Engine inside a minimal Lima VM and exposes a standard Docker socket, so the CLI, Compose files and Testcontainers all work unchanged. Rancher Desktop and Podman Desktop are the other established open-source options, and OrbStack is a paid alternative noted for speed and low resource use. All of them exist largely because Docker Desktop requires a subscription for commercial use above roughly 250 employees or $10M revenue."},{"question":"How much RAM should I give Docker Desktop on a Mac?","answer":"Start at about a quarter of your physical memory and adjust from what you actually see. On a 16 GB machine, 4 GB suits a couple of services and 8 GB is reasonable if you run a database and several containers at once. Watch real consumption with docker stats rather than guessing. Over-allocating is a genuine cost on macOS, because the VM holds that memory whether containers use it or not, and the system has less for everything else."},{"question":"Why does "no matching manifest for linux/arm64" appear when pulling an image?","answer":"That image publishes no arm64 build, which is common for older or smaller third-party images, and databases in particular. Run it under emulation with docker run --platform linux/amd64 <image>, accepting that it will be slower. Check whether a newer tag is multi-arch first, docker manifest inspect <image> lists the platforms available, since most widely used images added arm64 support once Apple Silicon became common."}]},{"slug":"installing-docker-linux","title":"Installing Docker on Linux","seoTitle":"How to Install Docker on Linux: Ubuntu, Debian, Fedora","description":"Install Docker Engine on Ubuntu, Debian, CentOS, RHEL or Fedora with working commands, then run docker without sudo, enable it on boot and fix common errors.","content":"$36","difficulty":"Easy","duration":"25 min","keywords":["install docker linux","docker ubuntu","docker debian","docker centos","docker engine linux","docker installation ubuntu","install docker ubuntu 24.04","docker apt repository","docker without sudo","docker permission denied linux","docker daemon.json log rotation","docker selinux :z","uninstall docker linux","docker.io vs docker-ce"],"publishedDate":"2026-02-04","updatedDate":"2026-09-16","order":7,"tableOfContents":[{"id":"prerequisites","title":"Prerequisites","level":2},{"id":"linux-requirements","title":"Linux Requirements","level":3},{"id":"installing-docker-on-ubuntudebian","title":"Installing Docker on Ubuntu/Debian","level":2},{"id":"step-1-update-package-index","title":"Step 1: Update Package Index","level":3},{"id":"step-2-install-prerequisites","title":"Step 2: Install Prerequisites","level":3},{"id":"step-3-add-dockers-official-gpg-key","title":"Step 3: Add Docker's Official GPG Key","level":3},{"id":"step-4-set-up-repository","title":"Step 4: Set Up Repository","level":3},{"id":"step-5-install-docker-engine","title":"Step 5: Install Docker Engine","level":3},{"id":"step-6-verify-installation","title":"Step 6: Verify Installation","level":3},{"id":"installing-docker-on-centosrhel","title":"Installing Docker on CentOS/RHEL","level":2},{"id":"step-1-install-required-packages-centos","title":"Step 1: Install Required Packages (CentOS)","level":3},{"id":"step-2-add-docker-repository-centos","title":"Step 2: Add Docker Repository (CentOS)","level":3},{"id":"step-3-install-docker-centos","title":"Step 3: Install Docker (CentOS)","level":3},{"id":"step-4-start-docker-centos","title":"Step 4: Start Docker (CentOS)","level":3},{"id":"step-5-verify-installation-centos","title":"Step 5: Verify Installation (CentOS)","level":3},{"id":"installing-docker-on-fedora","title":"Installing Docker on Fedora","level":2},{"id":"step-1-install-prerequisites-fedora","title":"Step 1: Install Prerequisites (Fedora)","level":3},{"id":"step-2-add-docker-repository-fedora","title":"Step 2: Add Docker Repository (Fedora)","level":3},{"id":"step-3-install-docker-fedora","title":"Step 3: Install Docker (Fedora)","level":3},{"id":"step-4-start-docker-fedora","title":"Step 4: Start Docker (Fedora)","level":3},{"id":"step-5-verify-installation-fedora","title":"Step 5: Verify Installation (Fedora)","level":3},{"id":"post-installation-steps","title":"Post-Installation Steps","level":2},{"id":"running-docker-without-sudo","title":"Running Docker Without sudo","level":3},{"id":"configure-docker-to-start-on-boot","title":"Configure Docker to Start on Boot","level":3},{"id":"configure-docker-daemon","title":"Configure Docker Daemon","level":3},{"id":"troubleshooting-linux-installation","title":"Troubleshooting Linux Installation","level":2},{"id":"docker-service-not-starting","title":"Docker Service Not Starting","level":3},{"id":"permission-denied-error","title":"Permission Denied Error","level":3},{"id":"firewall-issues","title":"Firewall Issues","level":3},{"id":"storage-driver-issues","title":"Storage Driver Issues","level":3},{"id":"testing-your-installation","title":"Testing Your Installation","level":2},{"id":"1-check-docker-version","title":"1. Check Docker Version","level":3},{"id":"2-view-system-information","title":"2. View System Information","level":3},{"id":"3-run-a-test-container","title":"3. Run a Test Container","level":3},{"id":"4-list-running-containers","title":"4. List Running Containers","level":3},{"id":"5-stop-and-remove-the-container","title":"5. Stop and Remove the Container","level":3},{"id":"linux-specific-configuration","title":"Linux-Specific Configuration","level":2},{"id":"selinux-considerations-centosrhelfedora","title":"SELinux Considerations (CentOS/RHEL/Fedora)","level":3},{"id":"apparmor-considerations-ubuntudebian","title":"AppArmor Considerations (Ubuntu/Debian)","level":3},{"id":"resource-limits","title":"Resource Limits","level":3},{"id":"docker-compose-on-linux","title":"Docker Compose on Linux","level":2},{"id":"best-practices-for-linux","title":"Best Practices for Linux","level":2},{"id":"1-keep-docker-updated","title":"1. Keep Docker Updated","level":3},{"id":"2-monitor-resource-usage","title":"2. Monitor Resource Usage","level":3},{"id":"3-clean-up-regularly","title":"3. Clean Up Regularly","level":3},{"id":"4-configure-logging","title":"4. Configure Logging","level":3},{"id":"5-use-systemd-for-management","title":"5. Use systemd for Management","level":3},{"id":"uninstalling-docker","title":"Uninstalling Docker","level":2},{"id":"ubuntudebian","title":"Ubuntu/Debian","level":3},{"id":"centosrhelfedora","title":"CentOS/RHEL/Fedora","level":3},{"id":"where-to-go-next","title":"Where to go next","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"Should I install docker.io or docker-ce on Ubuntu?","answer":"docker-ce, from Docker's own repository. The docker.io package in Ubuntu's archive is maintained by Debian and typically trails upstream by a significant margin, sometimes missing BuildKit features and the Compose v2 plugin entirely. Adding Docker's repository, the GPG key and sources.list.d entry in the steps above, means you get current releases and ordinary apt upgrade keeps them current. Note also that the bare docker package is an unrelated system-tray application, not what you want."},{"question":"How do I run Docker without sudo on Linux?","answer":"sudo usermod -aG docker $USER, then log out and back in, or run newgrp docker to get a shell with the new membership immediately. The step people miss is that supplementary groups are attached at login, so the shell you ran usermod in still has the old list and will keep failing. Do understand the security implication: a member of the docker group can mount the host root filesystem into a privileged container, so this is equivalent to passwordless root on that machine. On a shared server, rootless mode is the better answer."},{"question":"Why does Docker keep filling up my disk?","answer":"Two causes, both fixable. Container logs are the sneaky one. The default json-file driver has no size cap, so stdout accumulates indefinitely under /var/lib/docker/containers. Set max-size and max-file in /etc/docker/daemon.json and restart the daemon, remembering that the setting applies to containers created afterwards. The other is unused images, build cache and dangling volumes: docker system df shows what is reclaimable and docker system prune -a --volumes removes it."},{"question":"Do I need Docker Desktop on Linux?","answer":"No. Linux runs containers natively against the host kernel, so Docker Engine alone does everything, and that is what servers and CI runners use. Docker Desktop for Linux exists and adds a GUI, a VM for consistency with colleagues on other platforms, and a bundled Kubernetes, but it also brings the commercial licence terms that Docker Engine does not have. Unless you specifically want those features, install the engine."},{"question":"Why do bind mounts give permission denied on CentOS, RHEL or Fedora?","answer":"SELinux. The host directory is labelled in a way the container's process type cannot read, so files that look perfectly readable are refused. Append :z to the mount to relabel it as shared between containers, or :Z for private to this one, -v /data:/data:z. Do not disable SELinux to work around it; the relabel option exists precisely for this and takes one character. The same applies to Podman on these distributions."},{"question":"How do I completely uninstall Docker from Linux?","answer":"Remove the packages, then the data directories, because purging the package deliberately leaves your images and volumes behind. On Debian and Ubuntu: sudo apt-get purge docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin, then sudo rm -rf /var/lib/docker /var/lib/containerd. On RHEL-family systems substitute dnf remove for the first command. That second step deletes every image, container and named volume on the machine, so confirm nothing there is wanted before running it."}]},{"slug":"key-concepts","title":"Key Docker Concepts","seoTitle":"Docker Concepts: Image, Container, Volume and Registry","description":"A plain-English guide to core Docker concepts: images, containers, Dockerfile, registry, volumes, networks, Compose, and how the client talks to the daemon.","content":"$37","difficulty":"Easy","duration":"10 min","keywords":["docker images","docker containers","dockerfile","docker registry","docker compose","docker concepts","docker terminology","image vs container","docker architecture explained","docker daemon client","docker layers explained","what is a docker registry","docker volumes vs bind mounts","docker glossary"],"publishedDate":"2026-02-04","updatedDate":"2026-09-16","order":8,"tableOfContents":[{"id":"the-one-line-version","title":"The one-line version","level":2},{"id":"image","title":"Image","level":2},{"id":"container","title":"Container","level":2},{"id":"dockerfile","title":"Dockerfile","level":2},{"id":"registry","title":"Registry","level":2},{"id":"volume","title":"Volume","level":2},{"id":"network","title":"Network","level":2},{"id":"docker-compose","title":"Docker Compose","level":2},{"id":"client-and-daemon","title":"Client and daemon","level":2},{"id":"the-vocabulary-condensed","title":"The vocabulary, condensed","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What is the difference between a Docker image and a container?","answer":"An image is a read-only template: the application, its runtime, its libraries and the metadata describing how to start it, stored as a stack of layers. A container is a running instance created from that image, with a thin writable layer on top for anything it changes. One image can back any number of containers simultaneously, each with its own writable layer, in the same way one class can have many instances. The practical consequence is that images persist and containers are disposable."},{"question":"Where does data written inside a container go?","answer":"Into the container's writable layer, which exists only as long as the container does. Remove the container and every file it created is removed with it, which is why a database restarted from the same image comes back empty. Mount a named volume at the path that matters (-v pgdata:/var/lib/postgresql/data) and the data lives outside the container lifecycle. Bind mounts do the same thing with a specific host directory, which is what you usually want in development."},{"question":"What is a Docker layer and why does it matter?","answer":"Each Dockerfile instruction produces one layer, a filesystem diff, and the image is those layers stacked into a single view. They matter for three reasons. They are shared, so images built on the same base store and transfer it once. They are cached, so ordering instructions from least to most frequently changing makes rebuilds dramatically faster. And they are immutable, so a file added in one layer and deleted in a later one still occupies space and remains extractable from the image."},{"question":"What is a Docker registry and is Docker Hub the only one?","answer":"A registry stores and distributes images, the way a Git host stores repositories. Docker Hub is the default, docker pull nginx with no prefix goes there, and hosts official images for most common software. It is not the only option, and most organisations push their own images to a private registry such as GitHub Container Registry, Amazon ECR, Google Artifact Registry or Azure Container Registry. Prefixing the image name with a hostname selects one: ghcr.io/acme/myapp:1.0."},{"question":"Do I need Docker Compose or Kubernetes?","answer":"Compose if you are running several containers on one machine. A local development stack, a small single-server deployment. It defines services, networks and volumes in one file and starts them together, and it is the right tool most of the time. Kubernetes when you need containers scheduled across multiple machines with rolling updates, self-healing and autoscaling. They solve different problems, and starting with Compose costs nothing if you later move: the concepts transfer directly."},{"question":"Why can't my container reach another container on localhost?","answer":"Because localhost inside a container refers to that container and nothing else. Each one has its own network namespace and its own loopback interface. Put both on the same user-defined network and use the container name as the hostname: docker network create appnet, start each with --network appnet, then connect to db:5432. Compose does this for you automatically, which is why service names work as hostnames there without any configuration."}]},{"slug":"docker-images","title":"Docker Images","seoTitle":"Docker Images Explained: Layers, Tags and Registries","description":"Learn what a Docker image is, how layers and tags work, how to shrink images, and how to reclaim disk space by removing dangling images and old tags.","content":"$38","difficulty":"Easy","duration":"15 min","keywords":["docker images","dockerfile","image layers","docker hub","image optimization","docker build","image registry","container images","reduce docker image size","docker image layers and caching","docker image vs container","dangling images docker","docker image prune","docker tag vs digest","docker history command","multi-stage build size","docker images taking up space"],"publishedDate":"2026-02-04","updatedDate":"2026-09-16","order":9,"tableOfContents":[{"id":"layers","title":"Layers","level":2},{"id":"layers-are-shared","title":"Layers are shared","level":3},{"id":"layers-are-cached","title":"Layers are cached","level":3},{"id":"layers-are-additive-deleting-does-not-shrink","title":"Layers are additive: deleting does not shrink","level":3},{"id":"tags-and-digests","title":"Tags and digests","level":2},{"id":"reducing-image-size","title":"Reducing image size","level":2},{"id":"dangling-images-and-reclaiming-disk","title":"Dangling images and reclaiming disk","level":2},{"id":"registries","title":"Registries","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What is the difference between a Docker image and a container?","answer":"An image is a read-only template made of stacked filesystem layers; a container is a running instance created from it with its own thin writable layer. One image can produce any number of containers, all sharing the same underlying layers, which is why running ten containers from one image consumes very little extra disk. Images are built and pulled; containers are run, stopped and removed."},{"question":"How do I reduce my Docker image size?","answer":"Three things, in order of impact. Switch to a smaller base, node:22-alpine is around 150 MB against roughly 1.1 GB for node:22. Use a multi-stage build so compilers and development dependencies stay in the build stage and never ship. Add a .dockerignore so node_modules, .git and local artifacts are not copied in. Then run docker history to see which instruction is actually adding the weight, because it is often not the one you would guess."},{"question":"Why doesn't deleting files reduce my Docker image size?","answer":"Because layers are additive and all of them ship together. A file created in one layer and removed in a later one is hidden by a whiteout marker, but its bytes remain in the earlier layer and are still part of the image. Anything downloaded and then deleted must be handled within a single RUN instruction chained with &&, so only the net result is committed. The same applies to package manager caches. Clean them in the same RUN that installed the packages."},{"question":"What are dangling images and is it safe to delete them?","answer":"They are the <none> entries in docker images. Previous builds whose tag was reassigned to a newer image, leaving them unreferenced. They are entirely safe to remove, and docker image prune does exactly that. They accumulate quickly during development because every rebuild of the same tag creates one, which is the usual reason disk usage climbs steadily and eventually produces "no space left on device"."},{"question":"Why did docker system prune not free much disk space?","answer":"Almost certainly container logs, which prune never touches. With the default json-file log driver and no rotation, a long-running container can write tens of gigabytes to /var/lib/docker/containers/<id>/<id>-json.log. Check that directory's size directly. Fix it by setting "log-opts": {"max-size": "10m", "max-file": "3"} in /etc/docker/daemon.json and restarting Docker. Named volumes are the other common culprit, since they are only removed with --volumes."},{"question":"Should I use the latest tag?","answer":"No. latest is not special. It is simply the default tag name, pointing at whatever the maintainer most recently pushed, which can change without warning. Deploying it means two deploys of the same nominal version can run different code, and a rollback has no specific artifact to return to. Pin an explicit version tag, and for anything security-sensitive pin the digest with image@sha256:..., which is a content hash and genuinely immutable."}]},{"slug":"docker-images-naming-tags","title":"Image Naming & Tags","seoTitle":"Docker Image Tags and Naming: Registry, Repo and Digest","description":"Understand how Docker image names and tags are structured, tag images with docker tag, avoid the latest trap, and pin exact versions using digests.","content":"$39","difficulty":"Easy","duration":"8 min","keywords":["docker image naming","docker tags","image versioning","docker registry","semantic versioning"],"publishedDate":"2026-02-04","updatedDate":"2026-02-04","order":10,"tableOfContents":[{"id":"docker-image-name","title":"Docker Image Name","level":2},{"id":"breaking-down-a-real-docker-image-name","title":"Breaking Down a Real Docker Image Name","level":2},{"id":"what-is-a-docker-tag","title":"What Is a Docker Tag?","level":2},{"id":"how-to-tag-docker-images-like-a-pro","title":"How to Tag Docker Images Like a Pro","level":2},{"id":"1-semantic-versioning-answers-what-version-is-this","title":"1. Semantic Versioning: answers "what version is this?"","level":3},{"id":"2-image-variant-answers-how-big-or-lean-is-it","title":"2. Image Variant: answers "how big or lean is it?"","level":3},{"id":"3-tag-by-environment-answers-where-does-this-go","title":"3. Tag by Environment: answers "where does this go?"","level":3},{"id":"4-tag-with-a-git-commit-hash-answers-what-exact-code-is-inside-it","title":"4. Tag with a Git Commit Hash: answers "what exact code is inside it?"","level":3},{"id":"quick-reference","title":"Quick Reference","level":3},{"id":"understanding-image-sources","title":"Understanding Image Sources","level":2},{"id":"the-most-permanent-way-to-reference-an-image-digests","title":"The Most Permanent Way to Reference an Image: Digests","level":2},{"id":"hands-on-practice-tag-an-image-yourself","title":"Hands-On Practice: Tag an Image Yourself","level":2},{"id":"key-takeaways","title":"Key Takeaways","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What happens if I don't specify a tag?","answer":"Docker automatically uses latest. This is fine for quick experiments but risky in production because latest can point to a different image version over time."},{"question":"Can I create my own tags for someone else's image?","answer":"Yes! The docker tag command lets you add your own tags to any image you've pulled. The tags only exist locally on your machine unless you push them to a registry."},{"question":"What's the difference between a registry and a repository?","answer":"A registry is the entire hosting platform (like Docker Hub). A repository is one specific image within that platform (like the nginx repo on Docker Hub). A registry can hold thousands of repositories."},{"question":"Are Alpine images always better?","answer":"Not always. Alpine images are smaller and faster to pull, but they use a different C library (musl instead of glibc), which can cause compatibility issues with some software. Test Alpine images thoroughly before using them in production."}]},{"slug":"docker-images-hub","title":"Docker Hub","seoTitle":"Docker Hub Explained: Pull Images and Avoid Rate Limits","description":"Learn what Docker Hub is, how to find official images, and how to work around pull rate limits. A practical intro to using the default Docker registry.","content":"$3a","difficulty":"Easy","duration":"7 min","keywords":["docker hub","image registry","official images","docker pull","docker search"],"publishedDate":"2026-02-04","updatedDate":"2026-02-04","order":11,"tableOfContents":[{"id":"what-is-docker-hub","title":"What Is Docker Hub?","level":2},{"id":"what-kind-of-images-can-you-find-there","title":"What Kind of Images Can You Find There?","level":2},{"id":"how-to-use-the-docker-hub-website","title":"How to Use the Docker Hub Website","level":2},{"id":"docker-hub-rate-limits-and-how-to-avoid-hitting-them","title":"Docker Hub Rate Limits (And How to Avoid Hitting Them)","level":2},{"id":"key-takeaways","title":"Key Takeaways","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"Do I need an account to use Docker Hub?","answer":"No, you can pull public images without logging in. But creating a free account increases your rate limit from 100 to 200 pulls every 6 hours, and lets you push your own images."},{"question":"How do I know which tag to use?","answer":"Check the Tags tab on Docker Hub for the image you want. For production, always use a specific version tag (like redis:7.2) rather than latest, since latest can change at any time."},{"question":"Are community images safe to use?","answer":"Some are, some aren't. Red flags include: no recent updates, very few stars/pulls, and no documentation. Always prefer official or verified publisher images when they exist."}]},{"slug":"docker-images-pull","title":"Docker Pull & Search","seoTitle":"docker pull and docker search: Download Images from Hub","description":"Use docker pull to download images by tag or digest, read the pull output, and find images with docker search. Includes a hands-on Docker Hub exercise.","content":"$3b","difficulty":"Easy","duration":"5 min","keywords":["docker pull","docker search","docker tag"],"publishedDate":"2026-02-04","updatedDate":"2026-02-04","order":12,"tableOfContents":[{"id":"how-to-pull-images","title":"How to Pull Images","level":2},{"id":"what-does-the-pull-output-actually-mean","title":"What Does the Pull Output Actually Mean?","level":2},{"id":"how-to-search-for-images","title":"How to Search for Images","level":2},{"id":"practice-search-and-pull-from-docker-hub","title":"Practice: Search and Pull from Docker Hub","level":2},{"id":"key-takeaways","title":"Key Takeaways","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"Do I need to run docker pull before docker run?","answer":"No - docker run automatically pulls the image if it isn't already on your machine. docker pull is useful when you want to download an image ahead of time, or update a locally cached image to its newest version."},{"question":"How do I see all the images I've already pulled?","answer":"Run docker images (or docker image ls). It lists every image stored locally along with its tag, ID, size, and when it was created."},{"question":"Why does my pull say Already exists for some layers?","answer":"That's Docker reusing a cached layer from a previously pulled image. It's completely normal and actually a good thing: it means Docker is being efficient and not re-downloading data you already have."},{"question":"What's the difference between a tag and a digest?","answer":"A tag (like redis:7.2) is a human-readable label that can be reassigned over time. A digest (like ubuntu@sha256:779...) is a cryptographic fingerprint that is permanently tied to one exact version of an image. Use digests when you need a guarantee that nothing will ever change."},{"question":"Why do some image names have a slash and others don't?","answer":"Images without a slash (like nginx) are official images maintained by Docker or the software vendor. Images with a slash (like bitnami/postgresql) belong to a user or organization. The slash separates the namespace from the image name."},{"question":"I'm on a Mac with Apple Silicon and the image isn't working. Why?","answer":"Some images are only built for linux/amd64 (Intel architecture). Pull with --platform linux/amd64 to explicitly request that version, and Docker Desktop will run it through emulation automatically."}]},{"slug":"docker-images-listing","title":"List & Inspect Images","seoTitle":"docker images, inspect and history: List Local Images","description":"Use docker images, docker inspect and docker history to list local images, read their metadata and layers, and check how much disk space Docker uses.","content":"$3c","difficulty":"Easy","duration":"6 min","keywords":["docker images","docker inspect","docker history","image metadata","image layers"],"publishedDate":"2026-02-04","updatedDate":"2026-02-04","order":13,"tableOfContents":[{"id":"list-inspect-images","title":"List & Inspect Images","level":2},{"id":"how-to-see-all-your-local-images","title":"How to See All Your Local Images","level":2},{"id":"how-to-inspect-whats-inside-an-image","title":"How to Inspect What's Inside an Image","level":2},{"id":"hands-on-practice-list-and-inspect-an-image","title":"Hands-On Practice: List and Inspect an Image","level":2},{"id":"how-to-see-an-images-layer-history","title":"How to See an Image's Layer History","level":2},{"id":"how-to-check-how-much-disk-space-docker-is-using","title":"How to Check How Much Disk Space Docker Is Using","level":2},{"id":"key-takeaways","title":"Key Takeaways","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What's the difference between docker images and docker image ls?","answer":"They do exactly the same thing. docker image ls is the newer, more structured syntax that Docker introduced as part of a broader command reorganization."},{"question":"Why does CREATED show a date from weeks ago even though I just pulled the image?","answer":"The CREATED date is when the image was built by whoever published it, not when you downloaded it. If you pulled nginx today but the image was built two weeks ago, you'll see two weeks ago."},{"question":"What are dangling images and should I delete them?","answer":"Dangling images are untagged layers left behind from previous builds. For example, when you rebuild an image and the old version loses its tag. They're generally safe to delete with docker image prune."},{"question":"Why do I see <missing> in docker history output?","answer":"It means the intermediate layer isn't stored as a separate image on your machine, only the final image is kept locally. This is normal and doesn't indicate anything is broken."},{"question":"The sizes in docker images add up to way more than my actual disk usage. Why?","answer":"Because shared layers are counted in each image's size but only stored once on disk. Two images that both use the same Alpine base will each show the Alpine layer in their size, but your disk only holds one copy of it. docker system df gives a more accurate picture of real disk usage."}]},{"slug":"docker-images-building","title":"Building Images","seoTitle":"docker build: How to Build a Docker Image Step by Step","description":"Learn how to build a Docker image with a Dockerfile and docker build. Covers core instructions, .dockerignore and a complete Python API example.","content":"$3d","difficulty":"Easy","duration":"10 min","keywords":["how to build a docker image","dockerfile tutorial for beginners","docker build command explained","dockerfile instructions explained","FROM RUN COPY CMD dockerfile","what is a dockerfile","docker build context","dockerignore file"],"publishedDate":"2026-02-04","updatedDate":"2026-02-04","order":14,"tableOfContents":[{"id":"what-is-a-dockerfile","title":"What Is a Dockerfile?","level":2},{"id":"the-core-dockerfile-instructions","title":"The Core Dockerfile Instructions","level":2},{"id":"from---choose-your-starting-point","title":"FROM - Choose Your Starting Point","level":3},{"id":"workdir---set-your-working-directory","title":"WORKDIR - Set Your Working Directory","level":3},{"id":"copy---bring-your-files-into-the-image","title":"COPY - Bring Your Files Into the Image","level":3},{"id":"run---execute-commands-during-the-build","title":"RUN - Execute Commands During the Build","level":3},{"id":"env---set-environment-variables","title":"ENV - Set Environment Variables","level":3},{"id":"arg---build-time-variables","title":"ARG - Build-Time Variables","level":3},{"id":"expose---document-your-port","title":"EXPOSE - Document Your Port","level":3},{"id":"cmd---the-default-startup-command","title":"CMD - The Default Startup Command","level":3},{"id":"entrypoint---lock-in-the-executable","title":"ENTRYPOINT - Lock In the Executable","level":3},{"id":"how-to-build-an-image","title":"How to Build an Image","level":2},{"id":"hands-on-practice-build-your-first-image","title":"Hands-On Practice: Build Your First Image","level":2},{"id":"the-dockerignore-file-dont-skip-this","title":"The .dockerignore File: Don't Skip This","level":2},{"id":"a-complete-real-world-example-python-api","title":"A Complete Real-World Example: Python API","level":2},{"id":"key-takeaways","title":"Key Takeaways","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"Does the order of instructions in a Dockerfile matter?","answer":"Yes, a lot. Docker processes instructions top to bottom, and each one builds on the previous. Order also affects caching, if an early instruction changes, all layers after it are rebuilt. This is why you copy and install dependencies before copying your app code."},{"question":"What's the difference between RUN and CMD?","answer":"RUN executes during the build. It is how you install software and set things up. CMD executes when someone runs a container from the finished image. A Dockerfile can have many RUN instructions but only one effective CMD."},{"question":"What's the difference between CMD and ENTRYPOINT?","answer":"CMD is the default command and can be easily overridden at runtime. ENTRYPOINT locks in the executable and if someone running the container can change the arguments but not the program itself. When in doubt, use CMD."},{"question":"Why does my build take so long the first time but faster after that?","answer":"Docker caches each layer. On the first build everything is downloaded and executed fresh. On subsequent builds, Docker reuses cached layers for any instruction that hasn't changed. If you change your app code but not your dependencies, Docker skips reinstalling them entirely."},{"question":"What does the . mean at the end of docker build?","answer":"It's the build context, the folder Docker sends to its build engine. All COPY paths in your Dockerfile are relative to this folder. It's almost always . (the current directory), but you can point it to any folder."},{"question":"Should I use CMD in exec form or shell form?","answer":"Always use exec form: CMD ["node", "server.js"]. Shell form runs your command inside /bin/sh -c, which can cause issues with signal handling (like graceful shutdown) and behaves differently across environments."}]},{"slug":"docker-images-layers","title":"Layers & Caching","seoTitle":"Docker Image Layers and Build Cache Explained","description":"See how Docker image layers and the build cache work, why Dockerfile order matters, and how to speed up docker build by avoiding unneeded cache misses.","content":"$3e","difficulty":"Easy","duration":"8 min","keywords":["docker image layers explained","docker build cache","how docker caching works","dockerfile optimization","speed up docker builds","docker layer caching tutorial","dockerfile order matters","docker build slow fix"],"publishedDate":"2026-02-04","updatedDate":"2026-02-04","order":15,"tableOfContents":[{"id":"what-are-docker-image-layers","title":"What Are Docker Image Layers?","level":2},{"id":"how-dockers-build-cache-works","title":"How Docker's Build Cache Works","level":2},{"id":"order-of-dockerfile-matters","title":"Order of Dockerfile Matters","level":2},{"id":"the-wrong-way-rebuilds-dependencies-every-single-time","title":"The wrong way, rebuilds dependencies every single time","level":3},{"id":"the-right-way-reuses-the-dependency-cache-unless-packages-change","title":"The right way, reuses the dependency cache unless packages change","level":3},{"id":"hands-on-practice-watch-the-cache-in-action","title":"Hands-On Practice: Watch the Cache in Action","level":2},{"id":"when-you-need-to-skip-the-cache","title":"When You Need to Skip the Cache","level":2},{"id":"cleaning-up-the-build-cache","title":"Cleaning Up the Build Cache","level":2},{"id":"key-takeaways","title":"Key Takeaways","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"Why does Docker rebuild everything after I change one file?","answer":"Because you're probably copying all your files (including source code) before installing dependencies. When COPY . . comes before RUN npm install, any file change invalidates the install layer. Fix it by copying your dependency manifest first, installing, then copying the rest of your code."},{"question":"How does Docker know if a layer has changed?","answer":"For COPY and ADD instructions, Docker compares the actual file contents, not just timestamps. For RUN instructions, Docker compares the instruction string itself. If the string is identical, Docker assumes the result is the same and uses the cache."},{"question":"Does the cache work across different machines?","answer":"Not by default. The local build cache lives only on your machine. In CI/CD pipelines you can use cache backends (like storing layers in a registry) to share cache across machines, but that's an advanced topic."},{"question":"Why do some layers show 0B in docker history?","answer":"Those are metadata-only instructions like WORKDIR, ENV, EXPOSE, and CMD. They don't add or modify any files in the image, so they take up no space, but they still create a layer."},{"question":"When should I use --no-cache?","answer":"Use it when you need to pull in external updates that Docker can't detect, most commonly when running apt-get update or pip install to get the latest security patches. Since the instruction string hasn't changed, Docker would normally use the cache and miss the new patches."}]},{"slug":"docker-images-multi-stage","title":"Multi-Stage Builds","seoTitle":"Docker Multi-Stage Builds: Smaller Production Images","description":"Shrink your Docker image with multi-stage builds. See how COPY --from works, follow real examples, and target a single stage with docker build --target.","content":"$3f","difficulty":"Easy","duration":"9 min","keywords":["docker multi-stage builds","reduce docker image size","dockerfile multi stage example","docker production image optimization","COPY --from dockerfile","docker build target stage","small docker images","docker build vs runtime image"],"publishedDate":"2026-02-04","updatedDate":"2026-02-04","order":16,"tableOfContents":[{"id":"why-single-stage-builds-are-a-problem","title":"Why Single-Stage Builds Are a Problem","level":2},{"id":"what-multi-stage-builds-do","title":"What Multi-Stage Builds Do","level":2},{"id":"how-copy---from-works","title":"How COPY --from Works","level":2},{"id":"real-world-examples","title":"Real-World Examples","level":2},{"id":"react-frontend","title":"React Frontend","level":3},{"id":"python-with-compiled-dependencies","title":"Python with Compiled Dependencies","level":3},{"id":"hands-on-practice-build-a-multi-stage-image","title":"Hands-On Practice: Build a Multi-Stage Image","level":2},{"id":"targeting-a-specific-stage","title":"Targeting a Specific Stage","level":2},{"id":"when-should-you-use-multi-stage-builds","title":"When Should You Use Multi-Stage Builds?","level":2},{"id":"key-takeaways","title":"Key Takeaways","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"Do multi-stage builds make the build slower?","answer":"The total build time may be slightly longer because Docker runs more steps. But the resulting image is much smaller, which means faster pushes to your registry, faster pulls on your servers, and faster deployments overall. The tradeoff is almost always worth it."},{"question":"Does --target skip earlier stages?","answer":"No, Docker still processes all stages up to the target in order, because later stages may depend on earlier ones. It just stops after the stage you specified instead of continuing to the end."},{"question":"Can I have more than two stages?","answer":"Absolutely. You can have as many stages as you need. A typical setup might have separate stages for dependencies, testing, building, and production. Each stage can copy from any previous stage."},{"question":"What happens to the intermediate build stages?","answer":"They're used during the build and then discarded. They don't become part of the final image. Docker may keep them in its build cache to speed up future builds, but they won't show up in docker images."},{"question":"Can I copy files from an external image, not just my own stages?","answer":"Yes. COPY --from=nginx:alpine /etc/nginx/nginx.conf . pulls a file directly from the official nginx image without you needing to build it yourself. This is useful for grabbing configuration files or binaries from well-known images."}]},{"slug":"docker-images-optimization","title":"Optimization","seoTitle":"How to Reduce Docker Image Size: 8 Optimization Tips","description":"Reduce Docker image size with slim base images, merged RUN layers, a .dockerignore file and non-root users. Eight practical steps with examples.","content":"$40","difficulty":"Easy","duration":"9 min","keywords":["docker image optimization","reduce docker image size","dockerfile best practices","docker security non-root user","alpine linux docker","docker vulnerability scanning","dockerignore best practices","docker production image tips"],"publishedDate":"2026-02-04","updatedDate":"2026-02-04","order":17,"tableOfContents":[{"id":"1-choose-the-right-base-image","title":"1. Choose the Right Base Image","level":2},{"id":"2-combine-related-run-commands","title":"2. Combine Related RUN Commands","level":2},{"id":"3-set-up-a-dockerignore-file","title":"3. Set Up a .dockerignore File","level":2},{"id":"4-dont-install-tools-you-dont-need-at-runtime","title":"4. Don't Install Tools You Don't Need at Runtime","level":2},{"id":"5-clean-up-in-the-same-layer","title":"5. Clean Up in the Same Layer","level":2},{"id":"6-pin-package-versions","title":"6. Pin Package Versions","level":2},{"id":"7-never-run-as-root","title":"7. Never Run as Root","level":2},{"id":"8-use-copy-instead-of-add","title":"8. Use COPY Instead of ADD","level":2},{"id":"9-use-multi-stage-builds","title":"9. Use Multi-Stage Builds","level":2},{"id":"10-scan-your-images-for-vulnerabilities","title":"10. Scan Your Images for Vulnerabilities","level":2},{"id":"key-takeaways","title":"Key Takeaways","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"Do I need to apply all of these techniques to every image?","answer":"No, prioritize based on impact. The biggest wins in order are: choosing a smaller base image, using multi-stage builds (if you have a build step), setting up .dockerignore, and combining RUN commands. The others are good habits worth building over time."},{"question":"Is Alpine always the best base image choice?","answer":"It's usually the smallest option, but not always the right one. Some packages behave differently under Alpine's musl libc vs the standard glibc. If you run into strange errors with an Alpine-based image, try the -slim variant instead as it is a Debian-based and much more compatible, while still being significantly smaller than the full image."},{"question":"My image is still large after switching to Alpine. Why?","answer":"The base image is just the starting point. If you're copying in node_modules or running apt-get install without cleaning up, those layers add to the total size regardless of the base. Check your layer sizes with docker history myimage to see where the bulk is coming from."},{"question":"How often should I scan my images?","answer":"At minimum, scan every time you build a new image in CI before deploying. For long-running production images, set up a scheduled scan (weekly is common) since new CVEs are discovered constantly even if your image hasn't changed."},{"question":"Is it safe to use scratch as a base image?","answer":"For statically compiled binaries like Go or Rust programs, yes, scratch is an empty image with nothing in it at all, which is as minimal as it gets. But it means no shell, no package manager, and no OS utilities, so debugging becomes much harder. It's best suited for simple, self-contained services in well-understood production environments."}]},{"slug":"docker-images-managing","title":"Managing Images","seoTitle":"Docker Tag, Push, Save and Remove Images (docker rmi)","description":"Manage local images with docker tag, docker rmi, docker push and docker save/load. Includes cleanup with docker image prune and the export vs save difference.","content":"$41","difficulty":"Easy","duration":"7 min","keywords":["docker tag image","docker rmi remove image","docker push to registry","docker save load image file","docker image prune","clean up docker images","managing docker images","docker system prune"],"publishedDate":"2026-02-04","updatedDate":"2026-02-04","order":18,"tableOfContents":[{"id":"tagging-images","title":"Tagging Images","level":2},{"id":"removing-images","title":"Removing Images","level":2},{"id":"cleaning-up-unused-images","title":"Cleaning Up Unused Images","level":2},{"id":"pushing-images-to-a-registry","title":"Pushing Images to a Registry","level":2},{"id":"saving-images-as-files","title":"Saving Images as Files","level":2},{"id":"hands-on-practice-tag-save-and-remove","title":"Hands-On Practice: Tag, Save, and Remove","level":2},{"id":"saveload-vs-exportimport-whats-the-difference","title":"save/load vs export/import What's the Difference?","level":2},{"id":"full-cleanup-starting-fresh","title":"Full Cleanup: Starting Fresh","level":2},{"id":"key-takeaways","title":"Key Takeaways","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"If I remove a tag, does it delete the image?","answer":"Only if it was the last tag pointing to that image. If the same image has multiple tags (latest, v1.0, production), removing one tag just removes that label, the image and its layers stay on disk until all tags pointing to it are removed."},{"question":"Why can't I remove an image that I'm not using?","answer":"A stopped container still holds a reference to the image it was created from. Run docker ps -a to see stopped containers, remove them with docker rm <id>, and then try removing the image again."},{"question":"What's the difference between docker image prune and docker image prune -a?","answer":"Without -a, it only removes dangling images, untagged leftovers from old builds. With -a, it removes any image not currently used by a running or stopped container. The -a version reclaims much more space but is more aggressive."},{"question":"Do I need to be logged in to push to Docker Hub?","answer":"Yes. Run docker login first. For CI/CD pipelines, use a personal access token instead of your password as it's more secure and can be revoked independently."},{"question":"Is docker save the same as making a backup?","answer":"It's a portable snapshot of your image that can be moved between machines without a registry. It's a reasonable backup approach for small setups, but for production workflows a registry (even a self-hosted one) is much more manageable at scale."}]},{"slug":"docker-containers","title":"Docker Containers","seoTitle":"Docker Containers: Lifecycle, Ports and Debugging","description":"Learn how Docker containers work, from docker run and the container lifecycle to port publishing, restart policies and why a container exits immediately.","content":"$42","difficulty":"Easy","duration":"15 min","keywords":"$2d:props:tutorial:keywords","publishedDate":"2026-02-04","updatedDate":"2026-09-16","order":19,"tableOfContents":[{"id":"the-lifecycle","title":"The lifecycle","level":2},{"id":"why-your-container-exits-immediately","title":"Why your container exits immediately","level":2},{"id":"publishing-ports","title":"Publishing ports","level":2},{"id":"environment-and-configuration","title":"Environment and configuration","level":2},{"id":"restart-policies","title":"Restart policies","level":2},{"id":"resource-limits","title":"Resource limits","level":2},{"id":"working-with-a-running-container","title":"Working with a running container","level":2},{"id":"stop-vs-kill","title":"stop vs kill","level":2},{"id":"cleaning-up","title":"Cleaning up","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"Why does my Docker container exit immediately?","answer":"Because its main process finished. A container runs exactly as long as PID 1, so anything that completes, or daemonises itself into the background, ends the container. Check docker ps -a for the exit code and docker logs <container> for output. Exit 0 means a clean finish, so the command simply ran and stopped; exit 127 means command not found, usually a typo or a binary missing from the image. For servers, keep the process in the foreground, such as nginx -g "daemon off;"."},{"question":"What is the difference between docker run and docker start?","answer":"docker run creates a new container from an image and starts it, so running the same command three times produces three containers. docker start restarts an existing, stopped container by name or ID, preserving its writable layer and configuration. If docker ps -a is filling up with stopped containers, that is run being used where start was intended, or a missing --rm flag on one-off commands."},{"question":"How does Docker port mapping work?","answer":"-p hostPort:containerPort. Host on the left, container on the right. So -p 8080:80 makes a container listening on 80 reachable at localhost:8080. Reversing them is extremely common and produces a mapping that silently forwards to a port nothing is listening on. Note that EXPOSE in a Dockerfile publishes nothing; it only documents the port. Use -p 127.0.0.1:8080:80 to bind to localhost only, since a plain -p binds all interfaces and Docker's own iptables rules can bypass a host firewall."},{"question":"How do I get a shell inside a Docker container?","answer":"docker exec -it <container> sh for a running container, using bash if the image has it. Many slim images do not. If the container has already exited, exec will not work; start a new one with an overridden command instead: docker run --rm -it --entrypoint sh <image>. That is the standard way to explore an image whose normal command exits immediately or crashes on start."},{"question":"Which restart policy should I use?","answer":"unless-stopped for most long-running services. It restarts the container after a crash or a Docker daemon restart, but respects a deliberate docker stop, whereas always will bring back a container you intentionally stopped the next time the daemon starts, which is rarely wanted. Use on-failure for batch work that should retry on error but not after a successful run, and leave the default no for one-off commands."},{"question":"How do I remove all stopped Docker containers?","answer":"docker container prune removes every stopped container in one command and reports how much space was reclaimed. To remove absolutely everything including running containers, docker ps -aq | xargs docker rm -f works but should be used carefully. The better habit is prevention: add --rm to any one-off docker run so the container deletes itself on exit, which stops the buildup entirely."}]},{"slug":"docker-containers-running","title":"Running Containers","seoTitle":"docker run Command Explained: Flags and Examples","description":"Learn the docker run command with real examples: detached and interactive mode, naming, port mapping, resource limits, restart policies and more.","content":"$43","difficulty":"Easy","duration":"10 min","keywords":["docker run command explained","docker run flags options","docker port mapping tutorial","docker detached mode","docker run interactive shell","docker restart policy","docker run resource limits","how to run a docker container"],"publishedDate":"2026-02-04","updatedDate":"2026-02-04","order":20,"tableOfContents":[{"id":"the-basic-syntax","title":"The Basic Syntax","level":2},{"id":"detached-vs-interactive-mode","title":"Detached vs. Interactive Mode","level":2},{"id":"running-in-the-background--d","title":"Running in the Background (-d)","level":3},{"id":"running-an-interactive-shell--it","title":"Running an Interactive Shell (-it)","level":3},{"id":"auto-remove-when-done---rm","title":"Auto-Remove When Done (--rm)","level":3},{"id":"naming-your-container","title":"Naming Your Container","level":2},{"id":"port-mapping","title":"Port Mapping","level":2},{"id":"overriding-the-default-command","title":"Overriding the Default Command","level":2},{"id":"setting-a-working-directory","title":"Setting a Working Directory","level":2},{"id":"resource-limits","title":"Resource Limits","level":2},{"id":"restart-policies","title":"Restart Policies","level":2},{"id":"hands-on-practice-run-a-named-container-with-port-mapping","title":"Hands-On Practice: Run a Named Container with Port Mapping","level":2},{"id":"listing-containers","title":"Listing Containers","level":2},{"id":"key-takeaways","title":"Key Takeaways","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What's the difference between docker run and docker start?","answer":"docker run creates a brand new container from an image and starts it. docker start restarts an existing container that was previously stopped. You use docker run the first time, and docker start to resume it later."},{"question":"If I use -d, how do I see what the container is doing?","answer":"Use docker logs <container-name> to see its output. Add -f to follow the logs in real time, just like tail -f. For example: docker logs -f my-web."},{"question":"What happens to a container when it finishes running?","answer":"By default it stops and sits in a "stopped" state, and you can see it with docker ps -a. It stays there until you remove it with docker rm. If you used --rm, it's deleted automatically the moment it exits."},{"question":"Can two containers use the same host port?","answer":"No. Only one process on your machine can listen on a given port at a time. If you try to run two containers both mapped to port 8080, the second one will fail with a "port already in use" error."},{"question":"What does -p 127.0.0.1:5432:5432 do differently from -p 5432:5432?","answer":"-p 5432:5432 binds to all network interfaces, and anyone on your network could potentially reach it. -p 127.0.0.1:5432:5432 binds only to your loopback interface, meaning only your own machine can connect. For databases, always use the 127.0.0.1 form unless you specifically need network access."},{"question":"What's the difference between --restart always and --restart unless-stopped?","answer":"Both restart automatically after a host reboot. The difference is what happens after a manual docker stop. With unless-stopped, a manual stop is respected, and the container stays stopped. With always, Docker will restart it again even after you stop it manually, which can be surprising and hard to control."}]},{"slug":"docker-containers-lifecycle","title":"Lifecycle Management","seoTitle":"Docker Stop vs Kill: Container Lifecycle Explained","description":"Understand docker stop vs kill and every container state. Start, restart, pause and rename containers, and read exit codes to see why one stopped.","content":"$44","difficulty":"Easy","duration":"7 min","keywords":["docker stop vs kill","docker container lifecycle","docker start stop restart","docker pause unpause","docker container states","docker stop graceful shutdown","docker kill command","manage docker containers"],"publishedDate":"2026-02-04","updatedDate":"2026-02-04","order":21,"tableOfContents":[{"id":"container-states-at-a-glance","title":"Container States at a Glance","level":2},{"id":"stopping-a-container-stop-vs-kill","title":"Stopping a Container: stop vs kill","level":2},{"id":"docker-stop","title":"docker stop","level":3},{"id":"docker-kill","title":"docker kill","level":3},{"id":"quick-decision-guide","title":"Quick Decision Guide","level":3},{"id":"starting-a-stopped-container","title":"Starting a Stopped Container","level":2},{"id":"restarting-a-container","title":"Restarting a Container","level":2},{"id":"pausing-a-container-without-stopping-it","title":"Pausing a Container (Without Stopping It)","level":2},{"id":"renaming-a-container","title":"Renaming a Container","level":2},{"id":"hands-on-practice-stop-check-and-restart","title":"Hands-On Practice: Stop, Check, and Restart","level":2},{"id":"understanding-exit-codes","title":"Understanding Exit Codes","level":2},{"id":"waiting-for-a-container-to-finish","title":"Waiting for a Container to Finish","level":2},{"id":"keeping-containers-running-automatically","title":"Keeping Containers Running Automatically","level":2},{"id":"key-takeaways","title":"Key Takeaways","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"If I docker stop a container and then docker start it, does it lose its data?","answer":"It depends on where the data is stored. Data inside the container's writable layer (files created inside the container) persists across stop/start cycles, the container is just paused, not deleted. But that data is lost if you delete the container with docker rm. For important data, always use volumes."},{"question":"Why does docker stop wait 10 seconds before force-killing?","answer":"That's the default grace period. The window of your application gets to shut down cleanly. If 10 seconds isn't enough (for example, a database doing a large flush), use docker stop --time 30 or more. You can also change the default in your Compose file."},{"question":"What's the difference between exit code 137 and 143?","answer":"Code 137 means SIGKILL was sent (force kill: either docker kill, an OOM event, or the OS terminating the process). Code 143 means SIGTERM was sent (docker stop), and the application exited in response to the polite request. If you see 137 on a container that should have been gracefully stopped, something went wrong during shutdown."},{"question":"Can I stop a container from inside it?","answer":"Yes, running exit in an interactive shell, or any command that causes the main process to exit, will stop the container. The exit code will reflect whether it exited cleanly or with an error."},{"question":"What happens to a paused container if the host restarts?","answer":"The container's in-memory state is lost as a host restart doesn't preserve RAM. The container will be in a stopped state when Docker starts back up, not a paused state."}]},{"slug":"docker-containers-interaction","title":"Exec & Interact","seoTitle":"docker exec: Run Commands Inside a Running Container","description":"Use docker exec -it to open a bash shell in a container, then copy files with docker cp and inspect processes with docker top, attach and inspect.","content":"$45","difficulty":"Easy","duration":"8 min","keywords":["docker exec","docker exec bash","shell into docker container","docker exec it","docker cp","docker attach","docker inspect","docker container debugging","run command in docker container","docker top"],"publishedDate":"2026-02-04","updatedDate":"2026-02-04","order":22,"tableOfContents":[{"id":"docker-exec","title":"docker exec","level":2},{"id":"how-to-open-a-shell-in-a-docker-container","title":"How to Open a Shell in a Docker Container","level":2},{"id":"useful-docker-exec-options","title":"Useful docker exec Options","level":2},{"id":"run-as-a-different-user","title":"Run as a Different User","level":3},{"id":"pass-environment-variables","title":"Pass Environment Variables","level":3},{"id":"start-in-a-specific-directory","title":"Start in a Specific Directory","level":3},{"id":"docker-cp","title":"docker cp","level":2},{"id":"docker-attach","title":"docker attach","level":2},{"id":"docker-inspect","title":"docker inspect","level":2},{"id":"docker-top","title":"docker top","level":2},{"id":"a-practical-debugging-workflow","title":"A Practical Debugging Workflow","level":2},{"id":"quick-reference","title":"Quick Reference","level":2},{"id":"key-takeaways","title":"Key Takeaways","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What's the difference between docker exec and docker attach?","answer":"docker exec creates a brand new process inside the container and your container keeps running normally when you exit. docker attach connects you to the container's main process (PID 1), so exiting or pressing Ctrl+C can stop the entire container."},{"question":"What if bash isn't available in my container?","answer":"Many lightweight images (especially Alpine-based ones) don't include bash. Try sh instead: docker exec -it my-container sh. If even sh isn't available, your image is likely a distroless image with no shell at all, and in that case, copy files out with docker cp or add a debug sidecar container."},{"question":"Can I docker cp from a stopped container?","answer":"Yes. docker cp works even when the container isn't running, which makes it useful for recovering logs or files from a crashed container."},{"question":"How do I exit a container shell without stopping it?","answer":"Type exit or press Ctrl+D. Since you opened the shell with docker exec, exiting only closes that shell process; the container and its main process keep running."},{"question":"Can I run multiple docker exec sessions at the same time?","answer":"Yes. Each docker exec call creates an independent process. You can have multiple terminal windows all exec'd into the same container simultaneously."},{"question":"Why can't I see some files when I exec into my container?","answer":"The container only has access to its own filesystem layer. Files that live on your host (outside of mounted volumes) won't appear inside the container. If you need to get a file in, use docker cp or mount a volume when starting the container."}]},{"slug":"docker-containers-logs","title":"Logs & Monitoring","seoTitle":"docker logs: View, Tail and Filter Container Logs","description":"Read container output with docker logs -f and --tail, filter it with grep, and monitor resource use with docker stats, events and health checks.","content":"$46","difficulty":"Easy","duration":"7 min","keywords":["docker logs","docker logs command","docker stats","container monitoring","stream docker logs","docker logs filter","docker events","docker healthcheck","container resource usage","docker logs grep"],"publishedDate":"2026-02-04","updatedDate":"2026-02-04","order":23,"tableOfContents":[{"id":"docker-logs","title":"docker logs","level":2},{"id":"filtering-docker-logs-with-grep","title":"Filtering Docker Logs with grep","level":2},{"id":"log-drivers","title":"Log Drivers","level":2},{"id":"docker-stats","title":"docker stats","level":2},{"id":"docker-events","title":"docker events","level":2},{"id":"container-health-checks","title":"Container Health Checks","level":2},{"id":"quick-reference","title":"Quick Reference","level":2},{"id":"key-takeaways","title":"Key Takeaways","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"Why do my docker logs show nothing even though the container is running?","answer":"Your application might be writing logs to a file inside the container instead of to stdout/stderr. Docker only captures output sent to those two streams. Check where your app writes its logs. You may need to symlink the log file to /dev/stdout, or configure the app to log to stdout."},{"question":"What's the difference between docker logs -f and docker attach?","answer":"docker logs -f streams the captured log output, it's read-only and safe. docker attach connects directly to the container's main process, which means pressing Ctrl+C can stop your container. Use docker logs -f for monitoring."},{"question":"What does exit code 137 mean?","answer":"Exit code 137 means the container was killed by the OS. It's almost always because it exceeded its memory limit (OOM kill). Check docker stats to see if your container is approaching its memory limit, and consider increasing it with --memory or optimizing your app's memory usage."},{"question":"Can I view logs from a stopped or crashed container?","answer":"Yes. docker logs works on stopped containers as long as they haven't been removed. Once you run docker rm, the logs are gone. If you need persistent logs beyond a container's lifecycle, configure a log driver like fluentd or awslogs."},{"question":"How do I stop docker logs -f without closing my terminal?","answer":"Press Ctrl+C. Unlike docker attach, this only stops the log stream and does not affect the container in any way."},{"question":"How often does HEALTHCHECK run?","answer":"By default every 30 seconds, with a 30-second timeout and 3 retries before marking the container unhealthy. You can customize all three with --interval, --timeout, and --retries in your Dockerfile."}]},{"slug":"docker-containers-networking","title":"Networking Basics","seoTitle":"Docker Networking: Bridge Networks and Port Publishing","description":"Learn how Docker networking works: bridge, host and user-defined networks, port publishing with -p, container DNS, and how EXPOSE differs from -p.","content":"$47","difficulty":"Easy","duration":"10 min","keywords":["docker networking","docker bridge network","docker port publishing","container communication docker","docker network create","docker dns","docker expose vs publish","docker network host","docker container connect","docker network inspect"],"publishedDate":"2026-02-04","updatedDate":"2026-02-04","order":24,"tableOfContents":[{"id":"docker-network-types","title":"Docker Network Types","level":2},{"id":"the-default-bridge-network","title":"The Default Bridge Network","level":2},{"id":"user-defined-bridge-networks","title":"User-Defined Bridge Networks","level":2},{"id":"port-publishing","title":"Port Publishing","level":2},{"id":"managing-networks","title":"Managing Networks","level":2},{"id":"expose-vs--p","title":"EXPOSE vs -p","level":2},{"id":"host-networking","title":"Host Networking","level":2},{"id":"custom-dns-options","title":"Custom DNS Options","level":2},{"id":"quick-reference","title":"Quick Reference","level":2},{"id":"key-takeaways","title":"Key Takeaways","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"Why can't my two containers talk to each other even though they're both running?","answer":"They're probably both on the default bridge network, where containers can only communicate by IP address, not by name. Create a user-defined network with docker network create, add both containers to it with --network, and they'll be able to reach each other by container name automatically."},{"question":"What's the difference between -p 8080:80 and -p 80?","answer":"-p 8080:80 maps host port 8080 to container port 80, and you choose the host port. -p 80 tells Docker to map container port 80 to a random available port on the host. Use docker port <container> to see what port was assigned."},{"question":"Do I need to publish a port for containers on the same network to communicate?","answer":"No. Containers on the same user-defined network can reach each other freely without any port publishing. Port publishing is only needed to make a service accessible from outside Docker which means from your browser, another machine, or the host itself."},{"question":"What does EXPOSE actually do if it doesn't open ports?","answer":"It's documentation. It tells anyone reading the Dockerfile what port the application listens on. Some tools (like Docker Compose and Docker Desktop) use it to suggest port mappings. But it has zero effect on actual network accessibility, only -p does that."},{"question":"Can a container be on more than one network?","answer":"Yes. You can connect a running container to additional networks with docker network connect. This is useful when you have a gateway or proxy container that needs to reach services on two separate isolated networks."},{"question":"What's the difference between bridge and host networking?","answer":"Bridge networking gives each container its own isolated network namespace. It has its own IP, and you control what's exposed via port publishing. Host networking removes that isolation completely; the container shares the host's network interfaces and ports directly. Bridge is safer and more flexible; host is faster but requires careful port management."}]},{"slug":"docker-containers-volumes","title":"Volumes & Storage","seoTitle":"Docker Volumes and Bind Mounts: Persist Container Data","description":"Keep data after a container is removed. Compare named volumes, bind mounts and tmpfs, then share, back up and restore volumes with working commands.","content":"$48","difficulty":"Easy","duration":"9 min","keywords":["docker volumes","docker bind mounts","docker persistent storage","docker volume create","docker volume mount","named volume vs bind mount","docker data persistence","docker volume backup","tmpfs docker","docker volume prune"],"publishedDate":"2026-02-04","updatedDate":"2026-02-04","order":25,"tableOfContents":[{"id":"why-container-storage-is-temporary","title":"Why Container Storage Is Temporary","level":2},{"id":"named-volumes","title":"Named Volumes","level":2},{"id":"bind-mounts","title":"Bind Mounts","level":2},{"id":"named-volume-vs-bind-mount","title":"Named Volume vs Bind Mount","level":2},{"id":"tmpfs-mounts","title":"tmpfs Mounts","level":2},{"id":"managing-volumes","title":"Managing Volumes","level":2},{"id":"sharing-a-volume-between-containers","title":"Sharing a Volume Between Containers","level":2},{"id":"backing-up-and-restoring-volumes","title":"Backing Up and Restoring Volumes","level":2},{"id":"quick-reference","title":"Quick Reference","level":2},{"id":"key-takeaways","title":"Key Takeaways","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"If I delete a container, does its volume get deleted too?","answer":"No. Named volumes persist independently of containers. The volume and its data stick around until you explicitly remove it with docker volume rm or docker volume prune. This is a common source of confusion for beginners, and also why docker volume prune is a useful cleanup command."},{"question":"What's the difference between a named volume and a bind mount?","answer":"A named volume is managed by Docker and you can use it by name and Docker handles where it lives on your host. A bind mount is a specific path on your host machine that you map into the container. Named volumes are more portable and better for persistent data; bind mounts are more convenient for development because you can see and edit the files directly."},{"question":"Why is my bind mount showing an empty directory inside the container?","answer":"If the container image already has files at the mounted path, the bind mount will hide them. Either pre-populate your host directory with the needed files, or use a named volume which merges with the image's existing content at that path."},{"question":"Can two containers write to the same volume at the same time?","answer":"They can, but it's risky. Concurrent writes to the same files from multiple containers can corrupt data as there's no built-in locking. The safe pattern is one writer and multiple read-only readers (using :ro)."},{"question":"How do I find where a named volume is stored on my host?","answer":"Run docker volume inspect <name> and look at the Mountpoint field. On Linux it's usually under /var/lib/docker/volumes/. On Mac and Windows, volumes live inside the Docker Desktop VM, so you can't browse them directly from Finder/Explorer, that's why named volumes are better than bind mounts for those platforms."},{"question":"What happens to my data if I run docker volume prune?","answer":"It permanently deletes all volumes that aren't currently attached to at least one container (running or stopped). Before running it, check docker volume ls and make sure you're not about to lose anything important. There's no undo."}]},{"slug":"docker-containers-env","title":"Env Vars & Config","seoTitle":"Docker Environment Variables: -e and --env-file","description":"Pass docker environment variables with the -e flag and --env-file, check them in a running container, and learn why secrets need a safer approach.","content":"$49","difficulty":"Easy","duration":"7 min","keywords":["docker environment variables","docker env file","docker -e flag","container configuration","docker secrets","docker ENV dockerfile","docker ARG vs ENV","pass env vars to docker container","docker inspect env vars","twelve-factor app docker"],"publishedDate":"2026-02-04","updatedDate":"2026-02-04","order":26,"tableOfContents":[{"id":"passing-environment-variables-with--e","title":"Passing Environment Variables with -e","level":2},{"id":"using-an-env-file-with---env-file","title":"Using an Env File with --env-file","level":2},{"id":"viewing-environment-variables-in-a-running-container","title":"Viewing Environment Variables in a Running Container","level":2},{"id":"setting-defaults-in-your-dockerfile","title":"Setting Defaults in Your Dockerfile","level":2},{"id":"arg-vs-env","title":"ARG vs ENV","level":3},{"id":"how-to-handle-secrets-safely","title":"How to Handle Secrets Safely","level":2},{"id":"docker-secrets","title":"Docker Secrets","level":3},{"id":"quick-reference","title":"Quick Reference","level":2},{"id":"key-takeaways","title":"Key Takeaways","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What's the difference between ARG and ENV in a Dockerfile?","answer":"ARG is only available during the image build process, and it can't be read by your running application. ENV sets a variable that persists into the running container and can be overridden at runtime with -e. Use ARG for build customization (version numbers, build flags) and ENV for runtime configuration defaults."},{"question":"Is it safe to put secrets in environment variables?","answer":"For local development, it's acceptable. For production, it's not ideal because environment variables show up in docker inspect, process listings, and crash dumps. Use Docker Secrets, Kubernetes Secrets, or a secrets manager for anything sensitive in production."},{"question":"Can I see what environment variables a container is running with?","answer":"Yes, run docker inspect <container> and look for the Env section, or run docker exec <container> env to print them from inside. This means anyone with Docker access on that host can see those values."},{"question":"What happens if the same variable is set in both --env-file and -e?","answer":"The -e flag wins. It takes precedence over values in the env file, so you can use --env-file as a base and selectively override specific variables with -e."},{"question":"Should I commit my .env file to Git?","answer":"Never commit a .env file that contains real credentials. Instead, commit a .env.example file with all the variable names but placeholder values, this documents what's needed without exposing anything sensitive."},{"question":"How do I pass environment variables in Docker Compose?","answer":"In your docker-compose.yml, use the environment key for inline values or env_file to load from a file. It's the same concepts as -e and --env-file in docker run."}]},{"slug":"docker-containers-cleanup","title":"Cleanup & Pruning","seoTitle":"docker system prune: Clean Up Containers and Images","description":"Free disk space with docker system prune, docker rm and docker image prune. Learn what each command deletes, what is safe, and how to automate cleanup.","content":"$4a","difficulty":"Easy","duration":"6 min","keywords":["docker cleanup","docker prune","docker system prune","docker rm","remove docker containers","docker free disk space","docker image prune","docker volume prune","docker builder prune","docker system df"],"publishedDate":"2026-02-04","updatedDate":"2026-02-04","order":27,"tableOfContents":[{"id":"see-whats-taking-up-space-first","title":"See What's Taking Up Space First","level":2},{"id":"removing-containers","title":"Removing Containers","level":2},{"id":"remove-all-stopped-containers-at-once","title":"Remove All Stopped Containers at Once","level":3},{"id":"--rm","title":"--rm","level":3},{"id":"removing-images","title":"Removing Images","level":2},{"id":"removing-volumes","title":"Removing Volumes","level":2},{"id":"removing-networks","title":"Removing Networks","level":2},{"id":"build-cache-cleanup","title":"Build Cache Cleanup","level":2},{"id":"the-nuclear-option-docker-system-prune","title":"The Nuclear Option: docker system prune","level":2},{"id":"a-practical-cleanup-routine","title":"A Practical Cleanup Routine","level":2},{"id":"quick-reference","title":"Quick Reference","level":2},{"id":"key-takeaways","title":"Key Takeaways","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"How do I check how much disk space Docker is using?","answer":"Run docker system df. It gives a breakdown by object type (images, containers, volumes, build cache) with a RECLAIMABLE column showing how much you can safely free up."},{"question":"What's the difference between docker rm and docker container prune?","answer":"docker rm removes specific containers you name. docker container prune removes all stopped containers in one go. Use docker rm when you want to be selective; use prune when you want to bulk-clean everything that's no longer running."},{"question":"Will docker image prune delete images I still need?","answer":"docker image prune (without -a) only removes dangling images, untagged layers leftover from builds. It won't touch images that have a tag. Adding -a removes all images not currently used by any container (running or stopped), so if you have images you want to keep but aren't using right now, skip the -a flag."},{"question":"Is it safe to delete build cache?","answer":"Yes, always. Build cache just speeds up future builds. Deleting it means your next build takes longer (a cold build), but it won't break anything. Subsequent builds will recreate the cache layer by layer."},{"question":"What does --rm do and when should I use it?","answer":"--rm tells Docker to automatically delete the container as soon as it exits. Use it for anything you run once and don't need to revisit like one-off commands, scripts, tests, migrations. It's the best way to prevent stopped containers from piling up."},{"question":"I ran docker volume prune and lost my database data. How do I recover it?","answer":"Unfortunately, docker volume prune permanently deletes volume data and there's no built-in recovery. This is why checking docker volume ls before pruning is so important. Going forward, set up regular volume backups."}]},{"slug":"dockerfile-basics","title":"Dockerfile Basics","seoTitle":"Dockerfile Tutorial: Instructions and Best Practices","description":"Learn how to write a Dockerfile: FROM, COPY, RUN and CMD, layer caching, .dockerignore, multi-stage builds, HEALTHCHECK and the mistakes that bloat images.","content":"$4b","difficulty":"Easy","duration":"15 min","keywords":["dockerfile","dockerfile instructions","FROM COPY RUN CMD","dockerfile best practices","writing dockerfiles","how to write a dockerfile","dockerfile tutorial","dockerfile layer caching","dockerignore file","COPY vs ADD dockerfile","dockerfile multi-stage build","docker build command","dockerfile example nodejs"],"publishedDate":"2026-02-04","updatedDate":"2026-09-16","order":28,"tableOfContents":[{"id":"a-complete-example","title":"A complete example","level":2},{"id":"the-instructions","title":"The instructions","level":2},{"id":"from","title":"FROM","level":3},{"id":"copy-vs-add","title":"COPY vs ADD","level":3},{"id":"run-and-the-layer-problem","title":"RUN and the layer problem","level":3},{"id":"env-vs-arg","title":"ENV vs ARG","level":3},{"id":"expose-publishes-nothing","title":"EXPOSE publishes nothing","level":3},{"id":"user","title":"USER","level":3},{"id":"layer-caching-is-the-whole-game","title":"Layer caching is the whole game","level":2},{"id":"dockerignore","title":".dockerignore","level":2},{"id":"multi-stage-builds","title":"Multi-stage builds","level":2},{"id":"healthcheck","title":"HEALTHCHECK","level":2},{"id":"common-mistakes","title":"Common mistakes","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What is the difference between COPY and ADD in a Dockerfile?","answer":"COPY copies files and directories from the build context into the image, and does nothing else. ADD does the same but additionally fetches remote URLs and automatically extracts local tar archives. Both extras are surprising in practice. A .tar.gz you meant to copy verbatim gets unpacked instead. Use COPY for local files and RUN curl when you genuinely need to download something, which also lets you verify a checksum in the same layer."},{"question":"Why is my Docker build slow every time?","answer":"Almost certainly instruction order. Docker caches layers up to the first instruction whose inputs changed and rebuilds everything after it, so COPY . . placed before your dependency install means every source edit reinstalls all dependencies. Copy the package manifest and lockfile first, run the install, then copy the source. A missing .dockerignore compounds it by sending node_modules and .git into the build context on every build."},{"question":"What does EXPOSE actually do?","answer":"Only documentation. It records which port the containerised application listens on, for people reading the Dockerfile and for tooling such as docker run -P, which publishes all exposed ports to random host ports. It does not open a port, publish one, or affect networking in any way. Reaching the application from the host requires -p 3000:3000 at run time, and the container is reachable by other containers on the same network whether or not EXPOSE is present."},{"question":"What is the difference between ENV and ARG?","answer":"ARG defines a build-time variable available only while the image is being built, and it is gone at runtime. ENV sets an environment variable baked into the image and present in every container started from it. A critical caveat: ARG values appear in docker history, so they must never carry secrets. Use BuildKit secret mounts, which make a value available during a single RUN without recording it in any layer."},{"question":"Should I use a multi-stage build?","answer":"Almost always, for anything with a build step. Compilers, development dependencies and source code stay in the build stage and never reach the shipped image, which usually cuts size dramatically. A Go binary in a scratch image is a few megabytes against a 900 MB build environment. It also reduces attack surface, since a compiler in a production image is a tool an attacker can use. The cost is a few extra lines."},{"question":"Why does my container run as root and how do I change it?","answer":"Because that is the default when no USER instruction is given, and most base images do not set one. It is unnecessary for nearly every application and increases the damage a compromise can do, particularly combined with a mounted host path. Add USER node, or whichever non-root user your base image provides, after any instructions that need root, such as package installation. On Kubernetes you can additionally enforce it with runAsNonRoot: true, which turns a root image into a visible startup failure rather than a silent risk."}]},{"slug":"dockerfile-from","title":"Dockerfile FROM and Choosing a Base Image","seoTitle":"Dockerfile FROM: How to Choose a Base Image (alpine, slim)","description":"Choose the right Dockerfile FROM base image: alpine vs slim vs full, pinning tags and digests, multi-stage FROM, platform targeting and the scratch image.","content":"$4c","difficulty":"Easy","duration":"8 min","keywords":["dockerfile FROM","base image","alpine slim","multi-stage FROM","scratch image","docker base image best practices","alpine vs slim docker","distroless docker image","docker image size comparison","pin docker image digest","docker latest tag problem","FROM scratch golang","docker platform amd64 arm64","alpine python slow build"],"publishedDate":"2026-02-04","updatedDate":"2026-09-16","order":29,"tableOfContents":[{"id":"choose-the-variant-not-just-the-image","title":"Choose the variant, not just the image","level":2},{"id":"alpine-is-smaller-but-not-free","title":"Alpine is smaller but not free","level":3},{"id":"distroless-small-without-the-drawbacks","title":"Distroless: small without the drawbacks","level":3},{"id":"from-scratch","title":"FROM scratch","level":3},{"id":"pin-the-tag-then-pin-the-digest","title":"Pin the tag. Then pin the digest.","level":2},{"id":"multi-stage-each-from-starts-a-stage","title":"Multi-stage: each FROM starts a stage","level":2},{"id":"platform-targeting","title":"Platform targeting","level":2},{"id":"choosing-in-one-table","title":"Choosing, in one table","level":2},{"id":"common-mistakes","title":"Common mistakes","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"Should I use Alpine or slim for my Docker base image?","answer":"It depends entirely on the language. Alpine uses musl instead of glibc, which is fine for Go and Rust but costly for anything relying on prebuilt native binaries. For Python specifically, prefer -slim: PyPI's manylinux wheels are glibc-only, so Alpine forces pip to compile from source, which makes builds many times slower and often produces a larger image than the slim variant. Node.js sits in between. Alpine is fine until you need bcrypt or sharp, at which point -slim saves the trouble."},{"question":"Why shouldn't I use the latest tag in a Dockerfile?","answer":"Because a tag is a mutable pointer, not a fixed image. node:latest resolves to something different over time, so the same Dockerfile and the same source produce different images on different days, and a build can break with no change on your side. The same applies more subtly to node:22, which advances through 22.x. Pin a full version tag at minimum, and a digest when reproducibility genuinely matters. Then use Renovate or Dependabot to bump it, so updates arrive as reviewable pull requests."},{"question":"What is a distroless image and when should I use one?","answer":"A distroless image contains your application's runtime and nothing else, no shell, no package manager, no coreutils. It is glibc-based, so it avoids Alpine's musl compatibility problems while still being small, and the missing tooling removes most of what an attacker would use after gaining execution. The cost is debugging: docker exec ⦠sh fails because there is no shell. Use the :debug tag variant when you need one, or kubectl debug to attach an ephemeral container in Kubernetes."},{"question":"Why does my FROM scratch container exit immediately with "no such file or directory"?","answer":"The binary is dynamically linked and the file the kernel cannot find is the dynamic linker, not your program, which is why the message names nothing useful. Build statically: for Go that means CGO_ENABLED=0 go build, for Rust the musl target. Two other things scratch does not provide are TLS root certificates and timezone data, so copy /etc/ssl/certs/ca-certificates.crt from the build stage if your application makes HTTPS calls, or use gcr.io/distroless/static, which includes both."},{"question":"How do I pin a Docker base image to a digest?","answer":"Run docker inspect node:22-alpine --format='{{index .RepoDigests 0}}' to get the digest, then write FROM node:22-alpine@sha256:⦠in the Dockerfile. A digest is content-addressed, so it always resolves to the exact same bytes. Keep the human-readable tag alongside it for legibility. Pair this with Renovate or Dependabot, which raise a pull request when the underlying tag moves, that way you get reproducible builds and still receive security patches, rather than trading one for the other."},{"question":"What is the difference between multiple FROM lines and multiple Dockerfiles?","answer":"Multiple FROM lines create stages within a single build, and only the final stage becomes the image. Earlier stages exist purely so later ones can copy artefacts out with COPY --from=build, which is how compilers and dev dependencies stay out of what ships. That is almost always better than maintaining separate build and runtime Dockerfiles, because the stages share one file, one build command and one cache. Use --target when you want to stop at an intermediate stage for debugging or testing."}]},{"slug":"dockerfile-copy-add","title":"Dockerfile COPY vs ADD","seoTitle":"Dockerfile COPY vs ADD: Which to Use and When (Examples)","description":"Learn when to use COPY vs ADD in a Dockerfile. Covers build context, --chown and --chmod, COPY --from, the dependency-manifest pattern and .dockerignore.","content":"$4d","difficulty":"Easy","duration":"7 min","keywords":["dockerfile COPY","dockerfile ADD","dockerignore","COPY --chown","file permissions","COPY vs ADD","difference between COPY and ADD dockerfile","docker copy file into image","docker copy directory into image","COPY --from multi-stage","dockerignore example","COPY failed forbidden path outside the build context","docker permission denied non-root user","ADD checksum","docker build context size"],"publishedDate":"2026-02-04","updatedDate":"2026-09-16","order":30,"tableOfContents":[{"id":"the-build-context-decides-what-is-copyable","title":"The build context decides what is copyable","level":2},{"id":"copy-the-default-choice","title":"COPY: the default choice","level":2},{"id":"the-trailing-slash-trap","title":"The trailing-slash trap","level":3},{"id":"directory-copy-semantics","title":"Directory copy semantics","level":3},{"id":"copy---chown","title":"COPY --chown","level":2},{"id":"copy---chmod","title":"COPY --chmod","level":2},{"id":"copy---from","title":"COPY --from","level":2},{"id":"the-dependency-manifest-pattern","title":"The dependency-manifest pattern","level":2},{"id":"add-what-it-actually-does","title":"ADD: what it actually does","level":2},{"id":"when-to-prefer-run-curl-anyway","title":"When to prefer RUN curl anyway","level":3},{"id":"dockerignore","title":".dockerignore","level":2},{"id":"common-mistakes","title":"Common mistakes","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What is the difference between COPY and ADD in a Dockerfile?","answer":"COPY copies files and directories from the build context into the image and does nothing else. ADD does the same, and additionally extracts local tar archives automatically, downloads remote URLs, and clones Git repositories. The extras are why the default advice is COPY. An archive you meant to ship intact gets silently unpacked. Use ADD deliberately, for local tar extraction or a URL download you pin with --checksum, and COPY for everything else."},{"question":"Why does my COPY fail with "forbidden path outside the build context"?","answer":"Because the source path climbs above the directory you passed to docker build. Docker packages that directory and sends it to the daemon before any instruction runs, so anything outside it simply does not exist as far as COPY is concerned. Regardless of how valid the path looks in your shell. Build from a directory that contains everything you need and point at the Dockerfile with -f, for example docker build -f services/api/Dockerfile -t api . from the repository root."},{"question":"How do I fix "permission denied" after adding USER to my Dockerfile?","answer":"The copied files are still owned by root, and your non-root user cannot write to them. Add --chown to the COPY instructions, COPY --chown=node:node . ., which sets ownership as part of that layer at no cost. Avoid fixing it with a separate RUN chown -R, because that rewrites every file into a brand new layer and can roughly double the image size. If the user does not exist in the base image, create it with RUN adduser before the COPY that references it."},{"question":"Why is COPY saying a file is not found when it clearly exists?","answer":"Check .dockerignore first. An over-broad exclusion pattern removes the file from the build context, and the resulting error names the file rather than the rule that hid it, typically failed to compute cache key: "/package.json": not found. The other cause is the source path pointing outside the build context. Both mean the same thing: the daemon never received that file."},{"question":"Do I need a .dockerignore file?","answer":"Effectively yes, before your first COPY . .. Without one the entire directory is sent to the daemon on every build, so nodemodules and .git slow the build before an instruction runs, local .env files and private keys get baked into layers where anyone with the image can read them, and unrelated file changes invalidate your layer cache. At minimum exclude nodemodules/, .git/, .env* and your build output directory."},{"question":"What does COPY --from do?","answer":"It reads from somewhere other than the build context. Either an earlier stage in the same Dockerfile (COPY --from=build /app/dist ./dist) or a completely separate image (COPY --from=hashicorp/terraform:1.9 /bin/terraform /usr/local/bin/). The first is what makes multi-stage builds work, letting compilers and dev dependencies stay behind in the build stage. The second is often a cleaner way to obtain a binary than downloading and verifying it yourself, since the image is already versioned and pinnable."}]},{"slug":"dockerfile-run","title":"Dockerfile RUN","seoTitle":"Dockerfile RUN: Shell vs Exec Form and Cache Mounts","description":"Learn how the Dockerfile RUN instruction works, including shell vs exec form, apt-get best practices, same-layer cleanup, cache mounts, secrets and heredocs.","content":"$4e","difficulty":"Easy","duration":"8 min","keywords":["dockerfile RUN","build commands","layer efficiency","shell form exec form","apt-get dockerfile","RUN vs CMD dockerfile","docker reduce image size","RUN mount type cache","docker build cache apt","no-install-recommends","debian_frontend noninteractive docker","docker history layer size","dockerfile pipefail","combine RUN commands docker"],"publishedDate":"2026-02-04","updatedDate":"2026-09-16","order":31,"tableOfContents":[{"id":"shell-form-and-exec-form","title":"Shell form and exec form","level":2},{"id":"installing-packages","title":"Installing packages","level":2},{"id":"why-cleanup-must-be-in-the-same-layer","title":"Why cleanup must be in the same layer","level":2},{"id":"find-the-layer-that-did-it","title":"Find the layer that did it","level":3},{"id":"cache-mounts-make-rebuilds-fast","title":"Cache mounts make rebuilds fast","level":2},{"id":"secrets-belong-in-a-mount-not-a-layer","title":"Secrets belong in a mount, not a layer","level":2},{"id":"pipes-hide-failures","title":"Pipes hide failures","level":2},{"id":"heredocs-for-multi-command-blocks","title":"Heredocs for multi-command blocks","level":2},{"id":"creating-users-and-directories","title":"Creating users and directories","level":2},{"id":"cache-friendly-ordering","title":"Cache-friendly ordering","level":2},{"id":"common-mistakes","title":"Common mistakes","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What is the difference between RUN and CMD in a Dockerfile?","answer":"RUN executes during docker build and commits the result as an image layer, so its effects are baked in permanently. This is where you install packages and compile code. CMD sets the default command executed when a container starts from the finished image, and it produces no layer content. A Dockerfile normally has several RUN instructions and exactly one CMD. If you find yourself wanting to "run something when the container starts", that is CMD or ENTRYPOINT, not RUN."},{"question":"Why doesn't deleting files in a later RUN reduce my image size?","answer":"Because each instruction commits an immutable filesystem diff. A delete in a later layer writes a whiteout marker, which makes the file invisible inside the container while the actual bytes stay in the layer that created them and still travel with the image. Put the creation and the deletion in one RUN joined with &&, or move the whole step into a build stage that never ships. The same mechanism is why a secret written and later deleted is still recoverable from the published image."},{"question":"Why should apt-get update and apt-get install be in the same RUN?","answer":"Docker caches a RUN layer on its command string, not on what the command actually produced. As a separate instruction, RUN apt-get update keeps its cached layer indefinitely, so a later install runs against a package index that may be months old. Producing Version 'â¦' for 'â¦' was not found or silently installing outdated packages. Chaining them means any change to the install list re-runs the update too."},{"question":"How do I make Docker builds faster without making the image bigger?","answer":"Use BuildKit cache mounts. RUN --mount=type=cache,target=/root/.npm npm ci keeps the package cache on the builder between builds while writing nothing into the image, and the same works for pip, Go module and build caches, and apt. Combine that with ordering instructions from least to most frequently changing, and copying dependency manifests before source code. In CI you will also need a persistent or exported cache, since the mount lives on whichever builder ran the build."},{"question":"Why does my build succeed even though a command inside RUN failed?","answer":"Shell form runs under /bin/sh -c, and in a pipeline only the final command's exit status is reported. So RUN curl -fsSL https://⦠| sh succeeds even when curl 404s, because sh reads empty input and exits cleanly. Add SHELL ["/bin/bash", "-o", "pipefail", "-c"] near the top of the Dockerfile, or prefix the individual instruction with set -o pipefail &&. Using set -eux inside a heredoc block gives the same protection plus a readable build log."},{"question":"How do I find which instruction made my image so large?","answer":"docker history <image> --no-trunc lists every layer with its size and the instruction that created it, largest offenders usually being obvious at a glance. docker image inspect and tools like dive go further if you need to see individual files. Once you have identified it, the fix is normally one of three things: clean up inside the same RUN, add --no-install-recommends, or move the whole step into a build stage and copy only the artefact forward with COPY --from."}]},{"slug":"dockerfile-cmd-entrypoint","title":"Dockerfile CMD vs ENTRYPOINT","seoTitle":"Dockerfile CMD vs ENTRYPOINT: Differences and Examples","description":"Learn the difference between CMD and ENTRYPOINT in a Dockerfile, how to combine them, when to use exec vs shell form, and how to override both at runtime.","content":"$4f","difficulty":"Medium","duration":"14 min","keywords":["dockerfile CMD","dockerfile ENTRYPOINT","exec form shell form","container startup","default command","CMD vs ENTRYPOINT","difference between CMD and ENTRYPOINT","dockerfile entrypoint arguments","docker container not stopping SIGTERM","override CMD docker run","entrypoint script docker","docker exec form vs shell form"],"publishedDate":"2026-02-04","updatedDate":"2026-09-16","order":32,"tableOfContents":[{"id":"the-behavioural-difference","title":"The behavioural difference","level":2},{"id":"using-them-together","title":"Using them together","level":2},{"id":"exec-form-vs-shell-form","title":"Exec form vs shell form","level":2},{"id":"why-shell-form-breaks-graceful-shutdown","title":"Why shell form breaks graceful shutdown","level":3},{"id":"when-you-genuinely-need-the-shell","title":"When you genuinely need the shell","level":3},{"id":"entrypoint-scripts","title":"Entrypoint scripts","level":2},{"id":"overriding-at-runtime","title":"Overriding at runtime","level":2},{"id":"inheritance","title":"Inheritance","level":2},{"id":"choosing","title":"Choosing","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What is the difference between CMD and ENTRYPOINT in Docker?","answer":"ENTRYPOINT defines the executable that always runs; CMD defines the default arguments passed to it. The practical difference shows at runtime: arguments given to docker run replace CMD entirely but are appended to ENTRYPOINT. So an image with ENTRYPOINT ["python"] and CMD ["app.py"] runs python app.py by default and python other.py when you pass other.py. The executable is fixed and the caller controls the arguments."},{"question":"Should I use CMD or ENTRYPOINT?","answer":"Use both together for an image that is one specific tool: ENTRYPOINT fixes the binary, CMD provides overridable defaults. Use CMD alone for a general-purpose image where users may want to run arbitrary commands, such as a language runtime image. Use an ENTRYPOINT script ending in exec "$@" when setup must happen before the main process. Waiting for a dependency, rendering configuration, running migrations."},{"question":"What is the difference between exec form and shell form?","answer":"Exec form is the JSON array syntax, CMD ["node", "server.js"], and runs the binary directly as PID 1. Shell form, CMD node server.js, runs it via /bin/sh -c, making the shell PID 1 with your application as a child. Exec form is strongly preferred because only PID 1 receives signals, with shell form your application never sees SIGTERM and graceful shutdown silently never happens. Shell form does allow variable expansion and pipes, which exec form does not."},{"question":"Why does my container take 10 seconds to stop?","answer":"Because your application is not receiving SIGTERM, so docker stop waits the full grace period and then SIGKILLs it. The usual cause is shell form: /bin/sh is PID 1 and does not forward signals to its child. Switch to exec form so your process is PID 1. If you have an entrypoint script, make sure it ends with exec "$@" rather than plain "$@", since without exec the script remains PID 1 and absorbs the signal."},{"question":"How do I use an environment variable in CMD?","answer":"Exec form does not expand variables, CMD ["node", "app.js", "--port", "$PORT"] passes the literal string $PORT. The safe way to get expansion without losing signal handling is CMD ["sh", "-c", "exec node app.js --port $PORT"]. The exec is essential: it replaces the shell process with your application so it becomes PID 1. Plain shell form expands the variable but leaves the shell as PID 1 and breaks shutdown."},{"question":"How do I override ENTRYPOINT to debug a container?","answer":"docker run --entrypoint /bin/sh -it myimage replaces the entrypoint and drops you into a shell, which is the standard way to inspect an image whose entrypoint exits immediately or crashes. Note that --entrypoint accepts only the executable. Any arguments go after the image name. For a distroless image with no shell at all, copy the filesystem out with docker create and docker cp instead."}]},{"slug":"dockerfile-env-arg","title":"Dockerfile ENV vs ARG","seoTitle":"Dockerfile ENV vs ARG: Build-Time vs Runtime Variables","description":"Learn the difference between ENV and ARG in a Dockerfile, how ARG scoping works, why neither is safe for secrets, and how ARG affects the build cache.","content":"$50","difficulty":"Easy","duration":"7 min","keywords":["dockerfile ENV","dockerfile ARG","build arguments","environment variables","dockerfile variables","ARG vs ENV","difference between ARG and ENV dockerfile","docker build-arg","docker run -e environment variable","ARG before FROM","docker secrets build","docker history secret leak","TARGETARCH dockerfile","docker environment variable not set"],"publishedDate":"2026-02-04","updatedDate":"2026-09-16","order":33,"tableOfContents":[{"id":"the-core-difference","title":"The core difference","level":2},{"id":"env-runtime-configuration","title":"ENV: runtime configuration","level":2},{"id":"arg-build-time-parameters","title":"ARG: build-time parameters","level":2},{"id":"scoping-is-where-arg-surprises-people","title":"Scoping is where ARG surprises people","level":2},{"id":"arg-before-from","title":"ARG before FROM","level":3},{"id":"each-stage-needs-its-own-declaration","title":"Each stage needs its own declaration","level":3},{"id":"promoting-arg-to-env","title":"Promoting ARG to ENV","level":2},{"id":"why-arg-is-not-safe-for-secrets","title":"Why ARG is not safe for secrets","level":2},{"id":"arg-and-the-build-cache","title":"ARG and the build cache","level":2},{"id":"predefined-build-arguments","title":"Predefined build arguments","level":2},{"id":"common-mistakes","title":"Common mistakes","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What is the difference between ARG and ENV in a Dockerfile?","answer":"ARG defines a variable that exists only while docker build runs and is gone once the image is finished. Nothing inside a running container can see it. ENV writes a variable into the image so it is present during the remainder of the build and in every container started from that image. Use ARG for things that shape how the image is built, such as a base image version or a commit SHA, and ENV for configuration the application reads at runtime, such as a port or log level."},{"question":"Is it safe to pass a secret with --build-arg?","answer":"No. The value is recorded in the image's build metadata and docker history --no-trunc prints it back, even if the file you wrote it to was deleted in the same RUN. Anyone who can pull the image can recover it, and rotating the credential is the only fix after the fact. Use a BuildKit secret mount instead, RUN --mount=type=secret,id=token ⦠with docker build --secret id=token,env=TOKEN, which exposes the value to a single instruction and writes it to no layer."},{"question":"Why is my ARG empty after FROM?","answer":"Because the region before the first FROM is a separate scope, and build stages do not inherit from it. That scope exists so the base image itself can be parameterised. Add a bare ARG NODE_VERSION after FROM to bring the value into that stage, and repeat it in every stage of a multi-stage build that needs it. Docker gives no warning. An out-of-scope variable simply expands to an empty string."},{"question":"How do I override an environment variable at runtime?","answer":"docker run -e LOGLEVEL=debug myapp overrides whatever the Dockerfile's ENV set, with no rebuild. In Compose, use environment: for inline values or envfile: for a file. Precedence runs from most to least specific: docker run -e beats Compose environment:, which beats env_file:, which beats the Dockerfile's ENV. Treat ENV as the image's default rather than a fixed value."},{"question":"Why does changing --build-arg rebuild my whole image?","answer":"It does not rebuild the whole image. It invalidates the cache from the point the argument is first used, and everything above stays cached. If that feels like everything, the ARG is probably referenced earlier than it needs to be, often in a FROM line or an early RUN. Moving its first use further down keeps the expensive install layers cached. The same behaviour is useful deliberately: ARG CACHE_BUST placed just above an apt-get upgrade forces exactly that step to re-run."},{"question":"Should I use ENV or a .env file for configuration?","answer":"Different tools for different jobs. Dockerfile ENV bakes a default into the image, which is right for values that are part of the image itself: a PATH addition, PYTHONUNBUFFERED=1, a default port. A .env file is read by Docker Compose on your machine and never enters the image, which makes it right for per-environment values and for anything you would not publish. In production the orchestrator supplies the values, typically from a Kubernetes Secret or a parameter store, and the image's ENV defaults just fill the gaps."}]},{"slug":"dockerfile-workdir-expose","title":"Dockerfile WORKDIR, EXPOSE, USER and HEALTHCHECK","seoTitle":"Dockerfile WORKDIR, EXPOSE, USER and HEALTHCHECK","description":"Learn what WORKDIR, EXPOSE, USER, LABEL and HEALTHCHECK do in a Dockerfile, why EXPOSE publishes nothing, and when VOLUME causes problems in builds.","content":"$51","difficulty":"Easy","duration":"7 min","keywords":["dockerfile WORKDIR","dockerfile EXPOSE","dockerfile LABEL","dockerfile USER","dockerfile HEALTHCHECK","EXPOSE vs publish port docker","docker port not accessible","docker run as non-root user","dockerfile VOLUME anti-pattern","docker healthcheck kubernetes","OCI image labels","docker WORKDIR vs RUN cd","runAsNonRoot kubernetes numeric uid"],"publishedDate":"2026-02-04","updatedDate":"2026-09-16","order":34,"tableOfContents":[{"id":"workdir","title":"WORKDIR","level":2},{"id":"never-use-run-cd","title":"Never use RUN cd","level":3},{"id":"always-use-an-absolute-path","title":"Always use an absolute path","level":3},{"id":"the-permission-gotcha","title":"The permission gotcha","level":3},{"id":"expose-publishes-nothing","title":"EXPOSE publishes nothing","level":2},{"id":"user","title":"USER","level":2},{"id":"use-a-numeric-uid-if-you-deploy-to-kubernetes","title":"Use a numeric UID if you deploy to Kubernetes","level":3},{"id":"label","title":"LABEL","level":2},{"id":"volume-is-usually-a-mistake","title":"VOLUME is usually a mistake","level":2},{"id":"healthcheck","title":"HEALTHCHECK","level":2},{"id":"kubernetes-ignores-this-entirely","title":"Kubernetes ignores this entirely","level":3},{"id":"common-mistakes","title":"Common mistakes","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"Does EXPOSE actually open a port in Docker?","answer":"No. EXPOSE records which port the containerised application listens on, for people reading the Dockerfile and for tooling. Nothing more. It opens nothing, maps nothing, and a container without it is just as reachable. To reach the application from your host you need -p 8080:3000 at docker run, and containers on the same Docker network can reach each other regardless. The one functional use is docker run -P, which publishes every exposed port to a random host port."},{"question":"Why can't I reach my container even though I used -p?","answer":"Almost always because the application is bound to 127.0.0.1 inside the container. Localhost inside a container refers to that container alone, so Docker's port mapping delivers traffic to an interface nothing is listening on. Configure the server to bind 0.0.0.0, in Express that is app.listen(3000, '0.0.0.0'), in Flask --host=0.0.0.0. Confirm what is actually listening with docker exec <container> netstat -tlnp before changing anything else."},{"question":"Why does my container get "permission denied" after I added USER?","answer":"Usually because a directory was created after the USER instruction. WORKDIR creates directories owned by root regardless of the current user, so USER node followed by WORKDIR /app/cache leaves the application unable to write to its own cache. Create and chown the directory while still root, then drop privileges. The other common cause is copied files still owned by root, which COPY --chown=node:node fixes in the same layer."},{"question":"Should I use VOLUME in my Dockerfile?","answer":"Usually not, for application images. It creates an anonymous volume on every run that omits -v, and those pile up on the host until someone prunes them. Worse, any COPY or RUN writing to that path after the declaration is masked at runtime by the mount, so seed data silently disappears. Let whoever runs the container decide with -v or a Compose volumes: entry. It is defensible in database images, where the greater risk is someone running without persistence at all."},{"question":"Does Kubernetes use the Dockerfile HEALTHCHECK?","answer":"No, and this trips up a lot of teams. Kubernetes ignores the image's HEALTHCHECK completely and relies on livenessProbe, readinessProbe and startupProbe defined in the Pod spec. An image with a perfect healthcheck gets no scheduling, restart or traffic-routing benefit on Kubernetes. HEALTHCHECK remains useful for plain docker run, for Docker Swarm, and for Compose's dependson with condition: servicehealthy."},{"question":"What is the difference between WORKDIR and RUN cd?","answer":"RUN cd /app changes directory inside the shell that runs that one instruction, and that shell exits immediately afterwards, so the next instruction starts back at the previous directory. WORKDIR /app sets the directory for every subsequent instruction, persists into the final image as the container's starting directory, and creates the path if it does not exist. Use WORKDIR; the only place cd makes sense is chained within a single RUN, as in RUN cd /src && make install."}]},{"slug":"dockerfile-best-practices","title":"Dockerfile Best Practices","seoTitle":"Dockerfile Best Practices: Security, Size and Speed","description":"Write safer, smaller Dockerfiles. Covers exec form for SIGTERM, non-root users, secrets, layer order, multi-stage builds, pinned bases, scanning and hadolint.","content":"$52","difficulty":"Easy","duration":"10 min","keywords":["dockerfile best practices","dockerfile security","dockerfile linting","hadolint","image hardening","production dockerfile checklist","docker container SIGTERM not received","docker PID 1 zombie process","docker read-only filesystem","trivy scan docker image","docker scout cves","reduce docker image size","docker image security scanning CI","STOPSIGNAL dockerfile"],"publishedDate":"2026-02-04","updatedDate":"2026-09-16","order":35,"tableOfContents":[{"id":"1-exec-form-or-your-app-never-gets-sigterm","title":"1. Exec form, or your app never gets SIGTERM","level":2},{"id":"pid-1-has-no-default-signal-handlers","title":"PID 1 has no default signal handlers","level":3},{"id":"2-never-run-as-root","title":"2. Never run as root","level":2},{"id":"3-secrets-never-survive-deletion","title":"3. Secrets never survive deletion","level":2},{"id":"4-order-layers-by-change-frequency","title":"4. Order layers by change frequency","level":2},{"id":"5-multi-stage-builds","title":"5. Multi-stage builds","level":2},{"id":"6-pin-the-base-image","title":"6. Pin the base image","level":2},{"id":"7-scan-and-fail-the-build-on-it","title":"7. Scan, and fail the build on it","level":2},{"id":"8-lint-it","title":"8. Lint it","level":2},{"id":"9-health-checks-with-one-caveat","title":"9. Health checks, with one caveat","level":2},{"id":"10-label-for-provenance","title":"10. Label for provenance","level":2},{"id":"the-pre-merge-checklist","title":"The pre-merge checklist","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"Why doesn't my container stop gracefully on docker stop?","answer":"Almost certainly shell-form CMD. Written as CMD node server.js, Docker runs /bin/sh -c "node server.js", making the shell PID 1 and your application its child, and sh receives SIGTERM without forwarding it. After the grace period Docker sends SIGKILL and everything in flight is lost. Use exec form: CMD ["node", "server.js"]. If the process genuinely cannot handle signals, run with --init or add tini as the entrypoint."},{"question":"Is it really a problem to run a container as root?","answer":"It is a defence you are giving up for one line. Container isolation is good, so root inside a container is not automatically root on the host, but combined with a bind-mounted host path, a privileged flag, or a kernel vulnerability, the gap closes fast. Add USER 1000, and on Kubernetes pair it with runAsNonRoot: true, readOnlyRootFilesystem: true and dropped capabilities. Use a numeric UID, because runAsNonRoot cannot verify a named user and the Pod will refuse to start."},{"question":"How do I pass a secret to a Docker build safely?","answer":"With a BuildKit secret mount. RUN --mount=type=secret,id=npmtoken ⦠makes the value readable at /run/secrets/npmtoken for that one instruction and writes it to no layer, with docker build --secret id=npmtoken,env=NPMTOKEN supplying it. Do not use ARG or ENV: both are recorded in the image metadata and docker history --no-trunc prints them back, even when the file you wrote was deleted in the same RUN. Once a secret has shipped in an image, rotation is the only fix."},{"question":"What is the fastest way to make my Docker image smaller?","answer":"In order: use a multi-stage build so compilers and dev dependencies never ship, switch to a -slim or -alpine base variant (but not Alpine for Python), and make sure every cleanup happens in the same RUN as the thing it cleans up. Run docker history <image> --no-trunc first. It names the instruction responsible for each layer's size, which usually makes the answer obvious and saves you optimising something that was never the problem."},{"question":"Which Dockerfile linter should I use, and what should it fail on?","answer":"Hadolint is the standard, and it runs as a container so there is nothing to install: docker run --rm -i hadolint/hadolint < Dockerfile. In CI, set failure-threshold: warning. Its defaults are sensible and most of its rules map to genuine production problems, such as DL3002 for a root final user and DL3025 for shell-form CMD. Pair it with an image scanner such as Trivy or Docker Scout, and give that a non-zero exit code on HIGH and CRITICAL so results block a merge rather than scroll past in a log."},{"question":"Do I need a HEALTHCHECK in my Dockerfile?","answer":"Only if something reads it. docker run, Docker Swarm and Compose's dependson: condition: servicehealthy all do, and it is worth adding there. Kubernetes does not. It uses livenessProbe, readinessProbe and startupProbe from the Pod spec and ignores the image's healthcheck completely, so on a cluster the instruction buys you nothing and the probes are what you need to get right. Wherever you do use it, set --start-period generously or slow-starting applications are marked unhealthy while still booting."}]},{"slug":"dockerfile-real-world","title":"Real-World Dockerfile Examples","seoTitle":"Dockerfile Examples for Node.js, Python, Go and Java","description":"Copy production-ready Dockerfile examples for Node.js, Python FastAPI, Go, React with Nginx, Spring Boot and Django, and see the pattern they all share.","content":"$53","difficulty":"Easy","duration":"12 min","keywords":["dockerfile examples","node.js dockerfile","python dockerfile","go dockerfile","react dockerfile","production dockerfile example","typescript dockerfile multi-stage","fastapi dockerfile","django gunicorn dockerfile","spring boot dockerfile","nginx react dockerfile","dockerfile template","golang scratch dockerfile"],"publishedDate":"2026-02-04","updatedDate":"2026-09-16","order":36,"tableOfContents":[{"id":"nodejs-api-typescript","title":"Node.js API (TypeScript)","level":2},{"id":"python-fastapi","title":"Python FastAPI","level":2},{"id":"go-static-binary-on-scratch","title":"Go (static binary on scratch)","level":2},{"id":"react-spa-behind-nginx","title":"React SPA behind Nginx","level":2},{"id":"spring-boot-layered-jar","title":"Spring Boot (layered JAR)","level":2},{"id":"django-gunicorn","title":"Django + Gunicorn","level":2},{"id":"the-shape-they-share","title":"The shape they share","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What does a production-ready Dockerfile look like?","answer":"Five things, in every language: a build stage holding the compiler and dev dependencies, a runtime stage on the smallest base that can run the output, dependency manifests copied and installed before source so the expensive layer stays cached, a non-root numeric USER with ownership set via COPY --chown, and an exec-form CMD so the process receives SIGTERM. Add EXPOSE, OCI labels and a health check for tooling. Anything beyond that is tuning."},{"question":"Why is my multi-stage Node.js image still large?","answer":"Almost certainly because node_modules is being copied from the build stage. That directory was produced by npm ci, which installs devDependencies alongside production ones because the TypeScript compiler needs them, so copying it forward brings the entire dev tree into production. Copy only the build output, and run npm ci --omit=dev separately in the runtime stage. Copying package*.json first there keeps that install cached too."},{"question":"Should I use Alpine or slim for a Python Dockerfile?","answer":"-slim, in nearly every case. Alpine uses musl, and PyPI's prebuilt manylinux wheels target glibc, so pip install falls back to compiling from source. That turns a twenty-second install into several minutes, requires gcc and development headers in the image, and often produces a larger result than the slim variant. Alpine is an excellent choice for Go and Rust; for Python it usually costs on every axis you were trying to optimise."},{"question":"How do I pass environment-specific configuration to a React Docker image?","answer":"A build arg such as VITEAPIURL is compiled into the JavaScript bundle, which means one image per environment and a rebuild for every change. Fine if your pipeline builds per environment anyway. If you want a single image promoted from staging to production, serve a small /config.json that Nginx can have mounted or generated at container start, and have the app fetch it before rendering. Runtime environment variables cannot reach an already-built bundle."},{"question":"Why does my containerised app work locally but refuse connections in Docker?","answer":"The server is bound to 127.0.0.1, which inside a container means that container alone, so Docker's port mapping delivers traffic to an interface nothing is listening on. Bind 0.0.0.0 instead: --host 0.0.0.0 for Uvicorn, --bind 0.0.0.0:8000 for Gunicorn, app.listen(3000, '0.0.0.0') in Express. Confirm with docker exec <container> netstat -tlnp, which shows what is actually listening and on which address."},{"question":"Why does my Go container exit immediately with "no such file or directory"?","answer":"The binary is dynamically linked and the file the kernel cannot find is the dynamic linker, not your program, which is why the error names nothing useful. Build with CGO_ENABLED=0 to produce a fully static binary. While you are there, remember that scratch also lacks TLS root certificates and timezone data, so copy /etc/ssl/certs/ca-certificates.crt and /usr/share/zoneinfo from the build stage, or use gcr.io/distroless/static, which ships both."}]},{"slug":"docker-volumes","title":"Docker Volumes","seoTitle":"Docker Volumes: Named, Bind and tmpfs Mounts Compared","description":"Learn why container data disappears and how Docker volumes fix it. Compare the three mount types, handle permission problems, and back up or clean up volumes.","content":"$54","difficulty":"Easy","duration":"14 min","keywords":["docker volumes","docker named volumes","docker bind mounts","docker persistent storage","docker volume management","docker data disappears after restart","persist data docker container","docker volume vs bind mount","docker volume permission denied","backup docker volume","docker volume prune","tmpfs mount docker"],"publishedDate":"2026-02-04","updatedDate":"2026-09-16","order":37,"tableOfContents":[{"id":"why-container-data-disappears","title":"Why container data disappears","level":2},{"id":"the-three-mount-types","title":"The three mount types","level":2},{"id":"named-volumes","title":"Named volumes","level":2},{"id":"bind-mounts","title":"Bind mounts","level":2},{"id":"the-permissions-problem","title":"The permissions problem","level":2},{"id":"backing-up","title":"Backing up","level":2},{"id":"reclaiming-space","title":"Reclaiming space","level":2},{"id":"explore-further-in-this-section","title":"Explore further in this section","level":2},{"id":"quick-reference","title":"Quick reference","level":2},{"id":"key-takeaways","title":"Key takeaways","level":2},{"id":"check-your-understanding","title":"Check your understanding","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"Why does my Docker container lose data when it restarts?","answer":"It does not lose data on restart, docker stop and docker start preserve the container's writable layer. Data is lost when the container is removed, which is what happens when you deploy a new image or run docker rm. Since replacing containers is the normal deployment model, anything valuable must live in a volume mounted into the container rather than in its own filesystem."},{"question":"What is the difference between a volume and a bind mount?","answer":"A named volume is managed by Docker, referenced by name, and stored somewhere Docker chooses. A bind mount maps a specific host directory into the container. Use named volumes for persistent application data. They are portable across hosts, handle permissions better, and are considerably faster on macOS and Windows. Use bind mounts for development, where you want host edits reflected in the container immediately, and for mounting host configuration read-only."},{"question":"How do I fix "permission denied" on a Docker volume?","answer":"On Linux, the container preserves real user IDs rather than remapping them, so a container running as a non-root user cannot write to a host directory owned by you, and a root container creates root-owned files on your host. Run the container as your own user with docker run --user $(id -u):$(id -g), or set a matching USER in the Dockerfile. Named volumes largely avoid this because Docker sets ownership from the image's declared user on first use."},{"question":"How do I back up a Docker volume?","answer":"Use a throwaway container that mounts both the volume and a host directory, then archive one into the other: docker run --rm -v myvol:/data -v $(pwd):/backup alpine tar czf /backup/vol.tar.gz -C /data .. There is no built-in backup command. For databases, use the engine's own dump tool instead, pg_dump, mysqldump, because copying live database files can capture an inconsistent state that will not restore cleanly."},{"question":"Why didn't docker system prune free up my volume space?","answer":"Because volumes are excluded by default, deliberately, since they usually contain the data you would least want removed by a routine cleanup. Use docker volume prune to remove volumes no container references, or docker system prune --volumes to include them. Check docker system df -v first to see which volumes are actually consuming space, and be aware the deletion is permanent."},{"question":"What are anonymous volumes and why do I keep getting them?","answer":"When an image declares VOLUME /data in its Dockerfile and you run it without mounting anything at that path, Docker creates an unnamed volume with a random hexadecimal ID. Every docker run creates another, so repeatedly starting a database image leaves a trail of orphaned volumes each holding real data. Always mount a named volume explicitly at the documented path, and add --rm to one-off runs, which removes anonymous volumes when the container exits."}]},{"slug":"docker-volumes-named","title":"Named Volumes","seoTitle":"Docker Named Volumes vs Bind Mounts (With Examples)","description":"Create Docker named volumes with docker volume create, -v and --mount. See how data survives container removal and how to share a volume.","content":"$55","difficulty":"Easy","duration":"10 min read","keywords":["docker named volumes","docker volume create","docker volume ls","docker volume inspect","docker persistent storage","docker volume mount"],"publishedDate":"2026-07-12","updatedDate":"2026-07-12","order":38,"tableOfContents":[{"id":"what-are-named-volumes","title":"What Are Named Volumes?","level":2},{"id":"why-named-volumes-exist","title":"Why named volumes exist","level":3},{"id":"when-to-use-named-volumes","title":"When to Use Named Volumes","level":2},{"id":"creating-volumes-with-docker-volume-create","title":"Creating Volumes with docker volume create","level":2},{"id":"why-create-volumes-explicitly","title":"Why create volumes explicitly?","level":3},{"id":"mounting-volumes-with--v","title":"Mounting Volumes with -v","level":2},{"id":"example-persistent-application-data","title":"Example: persistent application data","level":3},{"id":"mounting-volumes-with---mount","title":"Mounting Volumes with --mount","level":2},{"id":"why-many-teams-prefer---mount","title":"Why many teams prefer --mount","level":3},{"id":"understanding-docker-run--v-myvolumeappdata","title":"Understanding docker run -v myvolume:/app/data","level":2},{"id":"persistence-across-container-removal","title":"Persistence Across Container Removal","level":2},{"id":"sharing-a-volume-between-multiple-containers","title":"Sharing a Volume Between Multiple Containers","level":2},{"id":"safe-sharing-patterns","title":"Safe sharing patterns","level":3},{"id":"inspecting-volumes-with-docker-volume-ls","title":"Inspecting Volumes with docker volume ls","level":2},{"id":"inspecting-details-with-docker-volume-inspect","title":"Inspecting Details with docker volume inspect","level":2},{"id":"important-fields","title":"Important fields","level":3},{"id":"where-docker-stores-volume-data-on-the-host","title":"Where Docker Stores Volume Data on the Host","level":2},{"id":"important-platform-note","title":"Important platform note","level":3},{"id":"volume-drivers","title":"Volume Drivers","level":2},{"id":"local-driver","title":"Local driver","level":3},{"id":"network-and-remote-drivers","title":"Network and remote drivers","level":3},{"id":"removing-volumes-with-docker-volume-rm","title":"Removing Volumes with docker volume rm","level":2},{"id":"why-removal-can-fail","title":"Why removal can fail","level":3},{"id":"cleaning-up-with-docker-volume-prune","title":"Cleaning Up with docker volume prune","level":2},{"id":"why-prune-carefully","title":"Why prune carefully","level":3},{"id":"named-volumes-vs-bind-mounts","title":"Named Volumes vs Bind Mounts","level":2},{"id":"practical-use-cases","title":"Practical Use Cases","level":2},{"id":"database-persistence","title":"Database persistence","level":3},{"id":"user-uploads-directory","title":"User uploads directory","level":3},{"id":"shared-logs-between-containers","title":"Shared logs between containers","level":3},{"id":"best-practices","title":"Best Practices","level":2},{"id":"use-descriptive-names","title":"Use descriptive names","level":3},{"id":"back-up-important-volumes","title":"Back up important volumes","level":3},{"id":"avoid-manual-host-edits-unless-necessary","title":"Avoid manual host edits unless necessary","level":3},{"id":"be-careful-with-shared-writes","title":"Be careful with shared writes","level":3},{"id":"clean-up-old-volumes-regularly","title":"Clean up old volumes regularly","level":3},{"id":"common-beginner-mistakes","title":"Common Beginner Mistakes","level":2},{"id":"final-takeaway","title":"Final Takeaway","level":2},{"id":"knowledge-check","title":"Knowledge Check","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What is a Docker named volume?","answer":"A storage area managed by Docker, referenced by a name rather than a host path. Docker decides where it physically lives, under /var/lib/docker/volumes/ on Linux, and handles its lifecycle. Named volumes are the recommended way to persist container data because they are portable across hosts, work identically on Linux, macOS and Windows, and can be backed up, inspected and managed with docker volume commands."},{"question":"What is the difference between a named volume and a bind mount?","answer":"A named volume is managed by Docker and referenced by name; a bind mount maps a specific host directory into the container. Named volumes are better for persistent application data, a database's files, uploaded content, because they are portable and Docker manages permissions and location. Bind mounts are better for development, where you want to edit files on the host and see changes inside the container immediately. Bind mounts are also noticeably slower on macOS and Windows."},{"question":"Where are Docker named volumes stored?","answer":"On Linux, under /var/lib/docker/volumes/<name>/_data. On macOS and Windows they live inside the Linux VM that Docker Desktop manages, so the path is not directly browsable from the host, which is deliberate, since the point of a named volume is that you do not manage its location. Use docker volume inspect <name> to see the mountpoint, and docker run --rm -v <name>:/data alpine ls /data to look inside."},{"question":"Do named volumes survive container deletion?","answer":"Yes. That is their purpose. Removing a container leaves its named volumes intact, so recreating the container reattaches the same data. This also means they accumulate: docker container prune does not remove them, and neither does plain docker system prune without the --volumes flag. Run docker volume ls periodically, and docker volume prune to remove volumes no container references."},{"question":"How do I back up a Docker volume?","answer":"Run a throwaway container that mounts both the volume and a host directory, then archive one into the other: docker run --rm -v myvolume:/data -v $(pwd):/backup alpine tar czf /backup/backup.tar.gz -C /data .. Restoring reverses it. For a database, prefer the engine's own dump tool over a filesystem copy, since copying live database files can capture an inconsistent state."},{"question":"Can multiple containers share a named volume?","answer":"Yes, and it is a common pattern. One container writes files and another reads them, or several replicas share a cache. Mount the same volume name in each. Be aware Docker provides no coordination: if two containers write to the same files concurrently you are responsible for avoiding corruption, exactly as you would be with two processes on a host. Most databases explicitly do not support multiple instances sharing one data directory."}]},{"slug":"docker-volumes-bind-mounts","title":"Bind Mounts","seoTitle":"Docker Bind Mounts: Mount a Host Folder in a Container","description":"Learn how to use a Docker bind mount with -v and --mount to share a host directory, make it read-only with :ro, and set up live reload for development.","content":"$56","difficulty":"Easy","duration":"10 min read","keywords":["docker bind mounts","docker mount host directory","docker -v host path","docker local development","docker live reload","bind mount vs volume"],"publishedDate":"2026-07-12","updatedDate":"2026-07-12","order":39,"tableOfContents":[{"id":"what-are-bind-mounts","title":"What Are Bind Mounts?","level":2},{"id":"why-bind-mounts-matter","title":"Why Bind Mounts Matter","level":2},{"id":"-v-hostpathcontainerpath-syntax","title":"-v /host/path:/container/path Syntax","level":2},{"id":"reading-the-syntax-correctly","title":"Reading the syntax correctly","level":3},{"id":"--mount-typebind-syntax","title":"--mount type=bind Syntax","level":2},{"id":"read-only-bind-mounts-with-ro","title":"Read-Only Bind Mounts with :ro","level":2},{"id":"why-read-only-mounts-matter","title":"Why read-only mounts matter","level":3},{"id":"live-code-reloading-the-classic-development-pattern","title":"Live Code Reloading: The Classic Development Pattern","level":2},{"id":"why-not-rebuild-the-image-for-every-change","title":"Why not rebuild the image for every change?","level":3},{"id":"common-development-example","title":"Common Development Example","level":2},{"id":"security-considerations","title":"Security Considerations","level":2},{"id":"a-container-can-modify-host-files","title":"A container can modify host files","level":3},{"id":"narrow-the-mount-when-possible","title":"Narrow the mount when possible","level":3},{"id":"prefer-read-only-where-possible","title":"Prefer read-only where possible","level":3},{"id":"be-extra-careful-in-production","title":"Be extra careful in production","level":3},{"id":"bind-mounts-vs-named-volumes","title":"Bind Mounts vs Named Volumes","level":2},{"id":"simple-decision-rule","title":"Simple decision rule","level":3},{"id":"common-use-case-development-environments","title":"Common Use Case: Development Environments","level":2},{"id":"common-use-case-configuration-injection","title":"Common Use Case: Configuration Injection","level":2},{"id":"common-use-case-log-collection","title":"Common Use Case: Log Collection","level":2},{"id":"absolute-vs-relative-paths","title":"Absolute vs Relative Paths","level":2},{"id":"absolute-paths","title":"Absolute paths","level":3},{"id":"relative-paths","title":"Relative paths","level":3},{"id":"why-path-clarity-matters","title":"Why path clarity matters","level":3},{"id":"what-happens-to-existing-container-files","title":"What Happens to Existing Container Files?","level":2},{"id":"permissions-and-ownership","title":"Permissions and Ownership","level":2},{"id":"practical-examples","title":"Practical Examples","level":2},{"id":"frontend-live-reload","title":"Frontend live reload","level":3},{"id":"read-only-config-file","title":"Read-only config file","level":3},{"id":"collecting-generated-reports","title":"Collecting generated reports","level":3},{"id":"best-practices","title":"Best Practices","level":2},{"id":"mount-only-the-minimum-necessary-path","title":"Mount only the minimum necessary path","level":3},{"id":"use-read-only-mounts-where-practical","title":"Use read-only mounts where practical","level":3},{"id":"prefer-absolute-paths-in-documentation-and-production-scripts","title":"Prefer absolute paths in documentation and production scripts","level":3},{"id":"use-bind-mounts-mainly-for-development-and-explicit-host-integration","title":"Use bind mounts mainly for development and explicit host integration","level":3},{"id":"remember-that-the-mount-hides-existing-files-at-the-target-path","title":"Remember that the mount hides existing files at the target path","level":3},{"id":"common-beginner-mistakes","title":"Common Beginner Mistakes","level":2},{"id":"final-takeaway","title":"Final Takeaway","level":2},{"id":"knowledge-check","title":"Knowledge Check","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"What is a Docker bind mount?","answer":"A direct mapping of a host directory or file into a container, written as -v /host/path:/container/path. Unlike a named volume, you control exactly where the data lives and can edit it with normal host tools. The container sees changes on the host immediately and vice versa, which is what makes bind mounts the standard choice for local development with live reload."},{"question":"When should I use a bind mount instead of a named volume?","answer":"Bind mounts for development, where editing source on the host and seeing it inside the container immediately is the whole point, and for mounting host configuration or certificates into a container. Named volumes for persistent application data such as database files, because Docker manages location and permissions, they are portable between hosts, and they perform far better on macOS and Windows. A rough rule: bind mounts for things you edit, named volumes for things the application writes."},{"question":"Why are bind mounts so slow on macOS and Windows?","answer":"Because Linux containers do not run natively there. They run inside a Linux VM that Docker Desktop manages, and every file access crosses the boundary between the host filesystem and that VM. For a large project with many small files, such as node_modules, the overhead is substantial. Mitigations include mounting only the source directory rather than the whole project, using a named volume for dependency directories, and enabling VirtioFS in Docker Desktop settings."},{"question":"Why are files created in a bind mount owned by root?","answer":"Because the container process runs as root by default, and a bind mount preserves real ownership on the host rather than remapping it. Files the container creates therefore appear as root-owned on your machine. Fix it by running the container as your own user, docker run --user $(id -u):$(id -g), or by setting a matching USER in the Dockerfile. This is a Linux-specific problem; Docker Desktop on macOS and Windows handles the mapping for you."},{"question":"Can I make a bind mount read-only?","answer":"Yes, by appending :ro, -v /host/config:/etc/app:ro. The container can read the files but cannot modify them, which is the right setting for mounted configuration, certificates and any host path the application has no business writing to. It is a cheap safeguard, particularly when mounting something sensitive, and it prevents a compromised container from tampering with host files it can see."},{"question":"Is it safe to bind-mount the Docker socket?","answer":"Rarely. Mounting /var/run/docker.sock gives the container full control of the Docker daemon, which on most setups is equivalent to root on the host. It can start a privileged container mounting the host filesystem. It is a common pattern for CI runners and monitoring agents, and it should be treated as granting host-level trust. Where possible use a socket proxy that restricts which API calls are permitted, rather than exposing the socket directly."}]},{"slug":"docker-volumes-management","title":"Volume Management","seoTitle":"docker volume ls, inspect, rm and prune: Manage Volumes","description":"Manage Docker volumes with docker volume ls, inspect, rm and prune. Learn how to back up a volume, restore it from a tar archive and migrate data between hosts.","content":"$57","difficulty":"Easy","duration":"8 min read","keywords":["docker volume management","docker volume prune","docker volume backup","docker volume restore","docker volume commands","docker volume inspect","docker volumes cleanup"],"publishedDate":"2026-07-12","updatedDate":"2026-07-12","order":40,"tableOfContents":[{"id":"listing-volumes-with-docker-volume-ls","title":"Listing Volumes with docker volume ls","level":2},{"id":"why-this-command-matters","title":"Why this command matters","level":3},{"id":"filtering-output","title":"Filtering output","level":3},{"id":"inspecting-volumes-with-docker-volume-inspect","title":"Inspecting Volumes with docker volume inspect","level":2},{"id":"what-the-important-fields-mean","title":"What the important fields mean","level":3},{"id":"removing-volumes-with-docker-volume-rm","title":"Removing Volumes with docker volume rm","level":2},{"id":"why-docker-volume-rm-sometimes-fails","title":"Why docker volume rm sometimes fails","level":3},{"id":"how-to-think-about-this-safely","title":"How to think about this safely","level":3},{"id":"cleaning-up-with-docker-volume-prune","title":"Cleaning Up with docker volume prune","level":2},{"id":"what-prune-actually-removes","title":"What prune actually removes","level":3},{"id":"when-prune-is-helpful","title":"When prune is helpful","level":3},{"id":"when-prune-is-dangerous","title":"When prune is dangerous","level":3},{"id":"backing-up-a-volume","title":"Backing Up a Volume","level":2},{"id":"breaking-the-backup-command-down","title":"Breaking the backup command down","level":3},{"id":"why-this-pattern-is-common","title":"Why this pattern is common","level":3},{"id":"restoring-a-volume-from-backup","title":"Restoring a Volume from Backup","level":2},{"id":"what-this-does","title":"What this does","level":3},{"id":"backup-and-restore-as-a-migration-tool","title":"Backup and Restore as a Migration Tool","level":2},{"id":"seeing-disk-usage-with-docker-system-df","title":"Seeing Disk Usage with docker system df","level":2},{"id":"why-docker-system-df-matters","title":"Why docker system df matters","level":3},{"id":"a-practical-cleanup-workflow","title":"A Practical Cleanup Workflow","level":2},{"id":"common-scenarios","title":"Common Scenarios","level":2},{"id":"scenario-1-a-volume-remove-fails","title":"Scenario 1: A volume remove fails","level":3},{"id":"scenario-2-disk-usage-keeps-growing","title":"Scenario 2: Disk usage keeps growing","level":3},{"id":"scenario-3-you-need-to-preserve-data-before-an-upgrade","title":"Scenario 3: You need to preserve data before an upgrade","level":3},{"id":"scenario-4-you-want-to-reuse-a-dataset-on-another-host","title":"Scenario 4: You want to reuse a dataset on another host","level":3},{"id":"best-practices","title":"Best Practices","level":2},{"id":"use-descriptive-volume-names","title":"Use descriptive volume names","level":3},{"id":"back-up-before-destructive-operations","title":"Back up before destructive operations","level":3},{"id":"inspect-before-deleting","title":"Inspect before deleting","level":3},{"id":"prune-intentionally","title":"Prune intentionally","level":3},{"id":"monitor-disk-usage-periodically","title":"Monitor disk usage periodically","level":3},{"id":"common-beginner-mistakes","title":"Common Beginner Mistakes","level":2},{"id":"final-takeaway","title":"Final Takeaway","level":2},{"id":"knowledge-check","title":"Knowledge Check","level":2},{"id":"frequently-asked-questions","title":"Frequently Asked Questions","level":2}],"noindex":false,"faqs":[{"question":"How do I list and inspect Docker volumes?","answer":"docker volume ls lists them, and docker volume inspect <name> shows the driver, mountpoint and any labels. To see what is actually inside one without a running container, mount it into a throwaway container: docker run --rm -v myvolume:/data alpine ls -la /data. That trick works for any volume regardless of platform, which matters on macOS and Windows where the real path lives inside the Docker VM and is not browsable from the host."},{"question":"Why does docker system prune not remove my volumes?","answer":"Because volumes are excluded by default, deliberately. They usually hold the data you least want to lose. docker system prune removes stopped containers, unused networks, dangling images and build cache, but leaves volumes alone unless you add --volumes. Use docker volume prune to remove volumes no container references, and check docker volume ls first, since a volume that looks unused may simply belong to a container you stopped."},{"question":"How do I find which container is using a volume?","answer":"docker ps -a --filter volume=<name> lists containers referencing it, including stopped ones. docker volume inspect does not show consumers, which is a common source of confusion. This matters before pruning, since a volume attached only to a stopped container still counts as in use and will not be removed, while one whose container was deleted becomes eligible immediately."},{"question":"How do I move a Docker volume to another host?","answer":"Archive it through a temporary container and transfer the archive: docker run --rm -v myvolume:/data -v $(pwd):/backup alpine tar czf /backup/vol.tar.gz -C /data ., then reverse the process on the destination after creating an empty volume there. There is no built-in volume migration command. For databases, use the engine's own dump and restore rather than a filesystem copy, which can capture an inconsistent state if the database is running."},{"question":"How do I reclaim disk space used by Docker volumes?","answer":"Start with docker system df -v, which breaks down usage per volume and shows which are unused. Then docker volume prune removes those with no container attached. Be deliberate. Unlike image pruning, this deletes data permanently and there is no recovery. Anonymous volumes created implicitly by images with a VOLUME instruction are a common hidden consumer, since each docker run creates a fresh one that nothing subsequently references."},{"question":"What are anonymous volumes and why do I have so many?","answer":"When an image declares VOLUME /data and you run it without mounting anything there, Docker creates an anonymous volume with a random hexadecimal name. Every docker run creates another, so repeatedly starting such an image, database images commonly do this, leaves a trail of orphaned volumes holding real data. Mount a named volume explicitly to avoid it, add --rm which removes anonymous volumes on exit, and run docker volume prune periodically to clear the ones already accumulated."}]}],"category":"docker","errorPages":[{"slug":"docker-container-name-already-in-use","title":"Conflict. The container name is already in use by container","description":"Fix "container name is already in use": run docker ps -a, then docker rm or docker rename the old container. Covers docker run --rm and Compose in CI."},{"slug":"docker-failed-to-solve-exit-code-1","title":"failed to solve: process "/bin/sh -c ..." did not complete successfully: exit code: 1","description":"Fix "failed to solve: process did not complete successfully: exit code 1": rebuild with --progress=plain and debug the failing RUN layer interactively."},{"slug":"docker-credential-helper-not-found","title":"docker-credential-desktop not installed or not available in PATH","description":"Fix "docker-credential-desktop not installed": remove credsStore from ~/.docker/config.json or install a helper. Covers CI and the Kubernetes variant."},{"slug":"docker-compose-depends-on-not-ready","title":"Connection refused between Compose services on startup","description":"Fix Compose "connection refused" at startup: add a healthcheck and depends_on with condition: service_healthy. Also covers migration gates and app retries."}],"children":"$L58"}]
58:["$","div",null,{"className":"max-w-none","children":"$L59"}]
59:[["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":["A container is a running instance of an image with its own writable layer, process namespace and ",["$","a",null,{"className":"text-teal-600 hover:text-teal-700 underline decoration-teal-300 hover:decoration-teal-500 transition-colors break-words","href":"/tutorials/linux/linux-networking","children":"network interface"}],". Images are templates; containers are what actually runs."]}],"\n",["$","h2",null,{"className":"text-xl sm:text-2xl font-bold text-slate-900 mb-3 mt-8 first:mt-0 scroll-mt-24 border-l-4 border-teal-500 pl-3 sm:pl-4","id":"the-lifecycle","children":"The lifecycle"}],"\n",["$","pre",null,{"className":"bg-slate-950 text-emerald-400 rounded-lg sm:rounded-xl p-3 sm:p-4 overflow-x-auto mb-4 border border-slate-700 shadow-lg text-xs sm:text-sm leading-relaxed [&_code]:bg-transparent [&_code]:p-0 [&_code]:text-inherit [&_code]:rounded-none","children":["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"docker run ââ⺠Running ââ⺠docker stop ââ⺠Exited ââ⺠docker rm ââ⺠gone\n â â²\n ââ pause âââââ\n"}]}],"\n",["$","pre",null,{"className":"bg-slate-950 text-emerald-400 rounded-lg sm:rounded-xl p-3 sm:p-4 overflow-x-auto mb-4 border border-slate-700 shadow-lg text-xs sm:text-sm leading-relaxed [&_code]:bg-transparent [&_code]:p-0 [&_code]:text-inherit [&_code]:rounded-none","children":["$","code",null,{"className":"language-bash text-sm","children":"docker run -d --name web nginx:1.27 # create + start\ndocker stop web # SIGTERM, then SIGKILL after 10s\ndocker start web # start it again. Same container\ndocker rm web # delete it\n"}]}],"\n",["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":["The distinction that matters: ",["$","strong",null,{"children":[["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"docker run"}]," creates a new container every time."]}]," ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"docker start"}]," restarts an existing one. Running the same command three times gives three containers, which is why ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"docker ps -a"}]," fills up unexpectedly."]}],"\n",["$","pre",null,{"className":"bg-slate-950 text-emerald-400 rounded-lg sm:rounded-xl p-3 sm:p-4 overflow-x-auto mb-4 border border-slate-700 shadow-lg text-xs sm:text-sm leading-relaxed [&_code]:bg-transparent [&_code]:p-0 [&_code]:text-inherit [&_code]:rounded-none","children":["$","code",null,{"className":"language-bash text-sm","children":"docker ps # running only\ndocker ps -a # including exited\n"}]}],"\n",["$","h2",null,{"className":"text-xl sm:text-2xl font-bold text-slate-900 mb-3 mt-8 first:mt-0 scroll-mt-24 border-l-4 border-teal-500 pl-3 sm:pl-4","id":"why-your-container-exits-immediately","children":"Why your container exits immediately"}],"\n",["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":"The most common Docker frustration, and it has one root cause."}],"\n",["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":["$L5a"," When PID 1 exits, the container exits. There is no background mode that keeps it alive."]}],"\n","$L5b","\n","$L5c","\n","$L5d","\n","$L5e","\n","$L5f","\n","$L60","\n","$L61","\n","$L62","\n","$L63","\n","$L64","\n","$L65","\n","$L66","\n","$L67","\n","$L68","\n","$L69","\n","$L6a","\n","$L6b","\n","$L6c","\n","$L6d","\n","$L6e","\n","$L6f","\n","$L70","\n","$L71","\n","$L72","\n","$L73","\n","$L74","\n","$L75","\n","$L76","\n","$L77","\n","$L78","\n","$L79","\n","$L7a","\n","$L7b","\n","$L7c","\n","$L7d","\n","$L7e","\n","$L7f","\n","$L80","\n","$L81","\n","$L82","\n","$L83","\n","$L84","\n","$L85","\n","$L86","\n","$L87","\n","$L88","\n","$L89","\n","$L8a","\n","$L8b","\n","$L8c","\n","$L8d","\n","$L8e","\n","$L8f","\n","$L90","\n","$L91","\n","$L92","\n","$L93","\n","$L94","\n","$L95","\n","$L96","\n","$L97","\n","$L98","\n","$L99","\n","$L9a"]
9f:I[18308,["8500","static/chunks/8500-98e13bcce54aa7a0.js","3839","static/chunks/3839-ab8b319d7c7091ea.js","5899","static/chunks/5899-9fb98f224ca30c06.js","3617","static/chunks/app/tutorials/docker/%5Bslug%5D/page-808e626485eefd0e.js"],"default"]
5a:["$","strong",null,{"children":"A container lives exactly as long as its main process."}]
5b:["$","pre",null,{"className":"bg-slate-950 text-emerald-400 rounded-lg sm:rounded-xl p-3 sm:p-4 overflow-x-auto mb-4 border border-slate-700 shadow-lg text-xs sm:text-sm leading-relaxed [&_code]:bg-transparent [&_code]:p-0 [&_code]:text-inherit [&_code]:rounded-none","children":["$","code",null,{"className":"language-bash text-sm","children":"docker run ubuntu # exits instantly\ndocker run ubuntu bash # also exits, no TTY, bash has no input and quits\ndocker run -it ubuntu bash # stays running. Interactive terminal attached\n"}]}]
5c:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":"The same applies to your own images. A web server that daemonises itself, forking into the background and returning, makes PID 1 exit, so Docker concludes the work is done:"}]
5d:["$","pre",null,{"className":"bg-slate-950 text-emerald-400 rounded-lg sm:rounded-xl p-3 sm:p-4 overflow-x-auto mb-4 border border-slate-700 shadow-lg text-xs sm:text-sm leading-relaxed [&_code]:bg-transparent [&_code]:p-0 [&_code]:text-inherit [&_code]:rounded-none","children":["$","code",null,{"className":"language-dockerfile text-sm","children":"# Wrong, nginx forks and the foreground process exits\nCMD ["nginx"]\n\n# Right. Stays in the foreground\nCMD ["nginx", "-g", "daemon off;"]\n"}]}]
5e:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":"Diagnosing it:"}]
5f:["$","pre",null,{"className":"bg-slate-950 text-emerald-400 rounded-lg sm:rounded-xl p-3 sm:p-4 overflow-x-auto mb-4 border border-slate-700 shadow-lg text-xs sm:text-sm leading-relaxed [&_code]:bg-transparent [&_code]:p-0 [&_code]:text-inherit [&_code]:rounded-none","children":["$","code",null,{"className":"language-bash text-sm","children":"docker ps -a # check the STATUS and exit code\ndocker logs <container> # what did it print before dying?\n"}]}]
60:["$","pre",null,{"className":"bg-slate-950 text-emerald-400 rounded-lg sm:rounded-xl p-3 sm:p-4 overflow-x-auto mb-4 border border-slate-700 shadow-lg text-xs sm:text-sm leading-relaxed [&_code]:bg-transparent [&_code]:p-0 [&_code]:text-inherit [&_code]:rounded-none","children":["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"STATUS\nExited (127) 2 seconds ago\n"}]}]
61:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":["Exit codes carry information: ",["$","strong",null,{"children":"0"}]," means clean exit, ",["$","strong",null,{"children":"1"}]," a general application error, ",["$","strong",null,{"children":"125"}]," the Docker command itself failed, ",["$","strong",null,{"children":"126"}]," the command is not executable, ",["$","strong",null,{"children":"127"}]," command not found, ",["$","strong",null,{"children":"137"}]," SIGKILL (often out of memory), ",["$","strong",null,{"children":"143"}]," SIGTERM."]}]
62:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":"Exit 127 nearly always means a typo in the command or a binary missing from the image."}]
63:["$","h2",null,{"className":"text-xl sm:text-2xl font-bold text-slate-900 mb-3 mt-8 first:mt-0 scroll-mt-24 border-l-4 border-teal-500 pl-3 sm:pl-4","id":"publishing-ports","children":"Publishing ports"}]
64:["$","pre",null,{"className":"bg-slate-950 text-emerald-400 rounded-lg sm:rounded-xl p-3 sm:p-4 overflow-x-auto mb-4 border border-slate-700 shadow-lg text-xs sm:text-sm leading-relaxed [&_code]:bg-transparent [&_code]:p-0 [&_code]:text-inherit [&_code]:rounded-none","children":["$","code",null,{"className":"language-bash text-sm","children":"docker run -d -p 8080:80 nginx:1.27\n# â âââ container port\n# âââ host port\n"}]}]
65:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":"Host on the left, container on the right. Reversing them is a rite of passage."}]
66:["$","pre",null,{"className":"bg-slate-950 text-emerald-400 rounded-lg sm:rounded-xl p-3 sm:p-4 overflow-x-auto mb-4 border border-slate-700 shadow-lg text-xs sm:text-sm leading-relaxed [&_code]:bg-transparent [&_code]:p-0 [&_code]:text-inherit [&_code]:rounded-none","children":["$","code",null,{"className":"language-bash text-sm","children":"docker run -d -p 127.0.0.1:8080:80 nginx # bind to localhost only\ndocker run -d -P nginx # publish all EXPOSEd ports randomly\ndocker port web # what is actually mapped?\n"}]}]
67:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":[["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"EXPOSE"}]," in a ",["$","a",null,{"className":"text-teal-600 hover:text-teal-700 underline decoration-teal-300 hover:decoration-teal-500 transition-colors break-words","href":"/tutorials/docker/dockerfile-basics","children":"Dockerfile"}]," publishes nothing. It is documentation. Only ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"-p"}]," or ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"-P"}]," at run time actually maps a port."]}]
68:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":["Binding to ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"127.0.0.1"}]," is worth knowing: without it, ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"-p 8080:80"}]," binds all interfaces, and on a cloud VM that can bypass your firewall, because Docker writes its own iptables rules."]}]
69:["$","h2",null,{"className":"text-xl sm:text-2xl font-bold text-slate-900 mb-3 mt-8 first:mt-0 scroll-mt-24 border-l-4 border-teal-500 pl-3 sm:pl-4","id":"environment-and-configuration","children":"Environment and configuration"}]
6a:["$","pre",null,{"className":"bg-slate-950 text-emerald-400 rounded-lg sm:rounded-xl p-3 sm:p-4 overflow-x-auto mb-4 border border-slate-700 shadow-lg text-xs sm:text-sm leading-relaxed [&_code]:bg-transparent [&_code]:p-0 [&_code]:text-inherit [&_code]:rounded-none","children":["$","code",null,{"className":"language-bash text-sm","children":"docker run -d \\n -e NODE_ENV=production \\n -e DATABASE_URL="postgres://db:5432/app" \\n --env-file .env \\n myapp:1.0\n"}]}]
6b:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":[["$","a",null,{"className":"text-teal-600 hover:text-teal-700 underline decoration-teal-300 hover:decoration-teal-500 transition-colors break-words","href":"/tutorials/linux/linux-environment-variables","children":"Environment variables"}]," are the standard way to configure a container, which is what lets one image serve every environment."]}]
6c:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":["Avoid passing secrets with ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"-e"}]," where you can. They appear in ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"docker inspect"}]," and in the process list on the host. A mounted file or a ",["$","a",null,{"className":"text-teal-600 hover:text-teal-700 underline decoration-teal-300 hover:decoration-teal-500 transition-colors break-words","href":"/tutorials/aws/aws-secrets-manager","children":"secrets manager"}]," is better."]}]
6d:["$","h2",null,{"className":"text-xl sm:text-2xl font-bold text-slate-900 mb-3 mt-8 first:mt-0 scroll-mt-24 border-l-4 border-teal-500 pl-3 sm:pl-4","id":"restart-policies","children":"Restart policies"}]
6e:["$","pre",null,{"className":"bg-slate-950 text-emerald-400 rounded-lg sm:rounded-xl p-3 sm:p-4 overflow-x-auto mb-4 border border-slate-700 shadow-lg text-xs sm:text-sm leading-relaxed [&_code]:bg-transparent [&_code]:p-0 [&_code]:text-inherit [&_code]:rounded-none","children":["$","code",null,{"className":"language-bash text-sm","children":"docker run -d --restart unless-stopped nginx:1.27\n"}]}]
6f:["$","div",null,{"className":"overflow-x-auto mb-4 -mx-4 sm:mx-0","children":["$","div",null,{"className":"min-w-full px-4 sm:px-0","children":["$","table",null,{"className":"min-w-full divide-y divide-slate-300 border border-slate-300 rounded-lg text-sm","children":[["$","thead",null,{"className":"bg-slate-100","children":["$","tr",null,{"children":[["$","th",null,{"className":"px-3 sm:px-6 py-2 sm:py-3 text-left text-xs sm:text-sm font-semibold text-slate-900 whitespace-nowrap","children":"Policy"}],["$","th",null,{"className":"px-3 sm:px-6 py-2 sm:py-3 text-left text-xs sm:text-sm font-semibold text-slate-900 whitespace-nowrap","children":"Behaviour"}]]}]}],["$","tbody",null,{"children":[["$","tr",null,{"children":[["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"no"}]}],["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":"Default (never restart)"}]]}],["$","tr",null,{"children":[["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"on-failure[:n]"}]}],["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":"Restart on non-zero exit, optionally capped"}]]}],["$","tr",null,{"children":[["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"always"}]}],["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":"Always restart, including after daemon restart"}]]}],["$","tr",null,{"children":[["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"unless-stopped"}]}],["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":["Like ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"always"}],", but stays stopped if you stopped it deliberately"]}]]}]]}]]}]}]}]
70:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":[["$","strong",null,{"children":[["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"unless-stopped"}]," is usually the right choice"]}]," for a long-running service. ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"always"}]," will restart a container you deliberately stopped when the daemon next starts, which is rarely what anyone wants."]}]
71:["$","h2",null,{"className":"text-xl sm:text-2xl font-bold text-slate-900 mb-3 mt-8 first:mt-0 scroll-mt-24 border-l-4 border-teal-500 pl-3 sm:pl-4","id":"resource-limits","children":"Resource limits"}]
72:["$","pre",null,{"className":"bg-slate-950 text-emerald-400 rounded-lg sm:rounded-xl p-3 sm:p-4 overflow-x-auto mb-4 border border-slate-700 shadow-lg text-xs sm:text-sm leading-relaxed [&_code]:bg-transparent [&_code]:p-0 [&_code]:text-inherit [&_code]:rounded-none","children":["$","code",null,{"className":"language-bash text-sm","children":"docker run -d --memory 512m --cpus 1.5 myapp:1.0\n"}]}]
73:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":"Without limits, one container can consume the whole host. A memory limit means the container is killed when it exceeds it, exit code 137, rather than the host running out of memory and the kernel choosing a victim arbitrarily."}]
74:["$","pre",null,{"className":"bg-slate-950 text-emerald-400 rounded-lg sm:rounded-xl p-3 sm:p-4 overflow-x-auto mb-4 border border-slate-700 shadow-lg text-xs sm:text-sm leading-relaxed [&_code]:bg-transparent [&_code]:p-0 [&_code]:text-inherit [&_code]:rounded-none","children":["$","code",null,{"className":"language-bash text-sm","children":"docker stats # live usage\ndocker stats --no-stream\n"}]}]
75:["$","h2",null,{"className":"text-xl sm:text-2xl font-bold text-slate-900 mb-3 mt-8 first:mt-0 scroll-mt-24 border-l-4 border-teal-500 pl-3 sm:pl-4","id":"working-with-a-running-container","children":"Working with a running container"}]
76:["$","pre",null,{"className":"bg-slate-950 text-emerald-400 rounded-lg sm:rounded-xl p-3 sm:p-4 overflow-x-auto mb-4 border border-slate-700 shadow-lg text-xs sm:text-sm leading-relaxed [&_code]:bg-transparent [&_code]:p-0 [&_code]:text-inherit [&_code]:rounded-none","children":["$","code",null,{"className":"language-bash text-sm","children":"docker logs -f web # follow\ndocker logs --tail 100 web\ndocker logs --since 10m web\n\ndocker exec -it web sh # shell inside\ndocker exec web env # single command\n\ndocker cp web:/etc/nginx/nginx.conf ./ # copy a file out\ndocker inspect web # full configuration as JSON\ndocker top web # processes\n"}]}]
77:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":[["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"docker exec"}]," attaches to an ",["$","strong",null,{"children":"already running"}]," container. If the container has exited, ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"exec"}]," fails. You need ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"docker run"}]," with a different command, or ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"--entrypoint"}]," to override what it starts:"]}]
78:["$","pre",null,{"className":"bg-slate-950 text-emerald-400 rounded-lg sm:rounded-xl p-3 sm:p-4 overflow-x-auto mb-4 border border-slate-700 shadow-lg text-xs sm:text-sm leading-relaxed [&_code]:bg-transparent [&_code]:p-0 [&_code]:text-inherit [&_code]:rounded-none","children":["$","code",null,{"className":"language-bash text-sm","children":"docker run --rm -it --entrypoint sh myapp:1.0\n"}]}]
79:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":"That is the standard way to explore an image whose normal command exits immediately."}]
7a:["$","h2",null,{"className":"text-xl sm:text-2xl font-bold text-slate-900 mb-3 mt-8 first:mt-0 scroll-mt-24 border-l-4 border-teal-500 pl-3 sm:pl-4","id":"stop-vs-kill","children":"stop vs kill"}]
7b:["$","pre",null,{"className":"bg-slate-950 text-emerald-400 rounded-lg sm:rounded-xl p-3 sm:p-4 overflow-x-auto mb-4 border border-slate-700 shadow-lg text-xs sm:text-sm leading-relaxed [&_code]:bg-transparent [&_code]:p-0 [&_code]:text-inherit [&_code]:rounded-none","children":["$","code",null,{"className":"language-bash text-sm","children":"docker stop web # SIGTERM, wait 10s, then SIGKILL\ndocker stop -t 30 web # allow 30 seconds\ndocker kill web # SIGKILL immediately\n"}]}]
7c:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":[["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"docker stop"}]," gives the application a chance to finish in-flight work. Use it. ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"docker kill"}]," is for a process that is genuinely stuck."]}]
7d:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":["If ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"docker stop"}]," always takes exactly 10 seconds, your application is not receiving ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"SIGTERM"}],", almost always because the Dockerfile used shell-form ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"CMD"}],", making ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"/bin/sh"}]," PID 1 instead of your process."]}]
7e:["$","h2",null,{"className":"text-xl sm:text-2xl font-bold text-slate-900 mb-3 mt-8 first:mt-0 scroll-mt-24 border-l-4 border-teal-500 pl-3 sm:pl-4","id":"cleaning-up","children":"Cleaning up"}]
7f:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":"Stopped containers accumulate and hold disk through their writable layers:"}]
80:["$","pre",null,{"className":"bg-slate-950 text-emerald-400 rounded-lg sm:rounded-xl p-3 sm:p-4 overflow-x-auto mb-4 border border-slate-700 shadow-lg text-xs sm:text-sm leading-relaxed [&_code]:bg-transparent [&_code]:p-0 [&_code]:text-inherit [&_code]:rounded-none","children":["$","code",null,{"className":"language-bash text-sm","children":"docker container prune # remove all stopped containers\ndocker rm -f web # force-remove a running one\ndocker ps -aq | xargs docker rm -f # remove everything, carefully\n"}]}]
81:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":["The best habit is preventing the buildup: ",["$","strong",null,{"children":[["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"--rm"}]," deletes the container the moment it exits."]}]]}]
82:["$","pre",null,{"className":"bg-slate-950 text-emerald-400 rounded-lg sm:rounded-xl p-3 sm:p-4 overflow-x-auto mb-4 border border-slate-700 shadow-lg text-xs sm:text-sm leading-relaxed [&_code]:bg-transparent [&_code]:p-0 [&_code]:text-inherit [&_code]:rounded-none","children":["$","code",null,{"className":"language-bash text-sm","children":"docker run --rm alpine echo hello\ndocker run --rm -it ubuntu bash\n"}]}]
83:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":"Use it for anything one-off. Tests, scripts, exploration. Nothing accumulates."}]
84:["$","h2",null,{"className":"text-xl sm:text-2xl font-bold text-slate-900 mb-3 mt-8 first:mt-0 scroll-mt-24 border-l-4 border-teal-500 pl-3 sm:pl-4","id":"quick-reference","children":"Quick reference"}]
85:["$","div",null,{"className":"overflow-x-auto mb-4 -mx-4 sm:mx-0","children":["$","div",null,{"className":"min-w-full px-4 sm:px-0","children":["$","table",null,{"className":"min-w-full divide-y divide-slate-300 border border-slate-300 rounded-lg text-sm","children":[["$","thead",null,{"className":"bg-slate-100","children":["$","tr",null,{"children":[["$","th",null,{"className":"px-3 sm:px-6 py-2 sm:py-3 text-left text-xs sm:text-sm font-semibold text-slate-900 whitespace-nowrap","children":"Task"}],["$","th",null,{"className":"px-3 sm:px-6 py-2 sm:py-3 text-left text-xs sm:text-sm font-semibold text-slate-900 whitespace-nowrap","children":"Command"}]]}]}],["$","tbody",null,{"children":[["$","tr",null,{"children":[["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":"Run detached with a port"}],["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"docker run -d -p 8080:80 --name <n> <image>"}]}]]}],["$","tr",null,{"children":[["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":"Run and auto-delete"}],["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"docker run --rm -it <image> <cmd>"}]}]]}],["$","tr",null,{"children":[["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":"List running"}],["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"docker ps"}]}]]}],["$","tr",null,{"children":[["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":"List all"}],["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"docker ps -a"}]}]]}],["$","tr",null,{"children":[["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":"Logs"}],["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"docker logs -f <n>"}]}]]}],["$","tr",null,{"children":[["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":"Shell inside"}],["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"docker exec -it <n> sh"}]}]]}],["$","tr",null,{"children":[["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":"Explore an image that exits"}],["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"docker run --rm -it --entrypoint sh <image>"}]}]]}],"$L9b","$L9c","$L9d","$L9e"]}]]}]}]}]
86:["$","hr",null,{}]
87:["$","h2",null,{"className":"text-xl sm:text-2xl font-bold text-slate-900 mb-3 mt-8 first:mt-0 scroll-mt-24 border-l-4 border-teal-500 pl-3 sm:pl-4","id":"key-takeaways","children":"Key takeaways"}]
88:["$","ul",null,{"className":"space-y-2 mb-4 ml-4 sm:ml-6 list-disc marker:text-teal-500","children":["\n",["$","li",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed pl-1","children":[["$","strong",null,{"children":"A container lives exactly as long as PID 1."}]," Exit immediately usually means the process daemonised or was not found."]}],"\n",["$","li",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed pl-1","children":[["$","strong",null,{"children":[["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"docker run"}]," creates a new container each time"]}],"; ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"docker start"}]," reuses one."]}],"\n",["$","li",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed pl-1","children":[["$","strong",null,{"children":["Port mapping is ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"-p host:container"}]]}],", and ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"EXPOSE"}]," publishes nothing."]}],"\n",["$","li",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed pl-1","children":[["$","strong",null,{"children":["Bind to ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"127.0.0.1"}]," when you do not want a port on all interfaces"]}],": Docker writes its own iptables rules and can bypass a host firewall."]}],"\n",["$","li",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed pl-1","children":[["$","strong",null,{"children":[["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"unless-stopped"}]," is the sensible restart policy"]}]," for services."]}],"\n",["$","li",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed pl-1","children":["$","strong",null,{"children":"Exit 137 is SIGKILL, usually out of memory; 127 is command not found."}]}],"\n",["$","li",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed pl-1","children":["$","strong",null,{"children":["A 10-second ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"docker stop"}]," every time means ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"SIGTERM"}]," is not reaching your process."]}]}],"\n",["$","li",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed pl-1","children":[["$","strong",null,{"children":["Use ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"--rm"}]," for anything one-off"]}]," so containers do not accumulate."]}],"\n"]}]
89:["$","hr",null,{}]
8a:["$","h2",null,{"className":"text-xl sm:text-2xl font-bold text-slate-900 mb-3 mt-8 first:mt-0 scroll-mt-24 border-l-4 border-teal-500 pl-3 sm:pl-4","id":"check-your-understanding","children":"Check your understanding"}]
8b:["$","$L9f",null,{"exercise":{"id":"docker-cont-exits","title":"A container that exits instantly","description":"docker run -d myapp returns an ID but docker ps shows nothing. docker ps -a shows "Exited (0)". What does this mean?","options":[{"id":"a","text":"The main process completed successfully and exited. A container only lives as long as PID 1, so it needs a foreground process"},{"id":"b","text":"The -d flag failed and the container never actually started"},{"id":"c","text":"The image is corrupt and needs rebuilding"},{"id":"d","text":"Docker ran out of memory and terminated the container"}],"correctId":"a","explanation":"Exit code 0 means the main process finished normally. Containers are not daemons. They run exactly as long as PID 1, so a command that completes ends the container. The usual causes are a service that daemonises itself into the background, or an image whose default command simply runs and exits. Fix it by keeping the process in the foreground, such as nginx -g "daemon off;".","wrongExplanations":{"b":"The container did start. It has an exit status, which only exists once a process has run and finished.","c":"A corrupt image fails at pull or create with an explicit error, not with a clean exit code of 0.","d":"An out-of-memory kill gives exit code 137, not 0, and would appear as OOMKilled in docker inspect."}}}]
8c:["$","$L9f",null,{"exercise":{"id":"docker-cont-portorder","title":"A port mapping that does nothing","description":"An app listens on port 3000 inside the container. The user runs docker run -d -p 3000:8080 myapp and cannot reach it on localhost:3000. Why?","options":[{"id":"a","text":"The mapping is backwards, -p is host:container, so this forwards host 3000 to container 8080 where nothing is listening"},{"id":"b","text":"The Dockerfile is missing an EXPOSE 3000 instruction"},{"id":"c","text":"Detached mode disables port publishing"},{"id":"d","text":"Ports below 1024 require elevated privileges"}],"correctId":"a","explanation":"The syntax is -p hostPort:containerPort, so 3000:8080 sends traffic arriving on host port 3000 to port 8080 inside the container. The application is on 3000 inside, so the correct mapping is -p 3000:3000, or any host port you prefer on the left. Reversing the two is one of the most common Docker mistakes.","wrongExplanations":{"b":"EXPOSE is documentation only and publishes nothing. Port mapping works with or without it.","c":"Detached mode only controls whether your terminal is attached. Publishing is unaffected.","d":"Both 3000 and 8080 are above 1024, and the privilege rule applies to the host side only."}}}]
8d:["$","hr",null,{}]
8e:["$","h2",null,{"className":"text-xl sm:text-2xl font-bold text-slate-900 mb-3 mt-8 first:mt-0 scroll-mt-24 border-l-4 border-teal-500 pl-3 sm:pl-4","id":"frequently-asked-questions","children":"Frequently Asked Questions"}]
8f:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":["$","strong",null,{"children":"Why does my Docker container exit immediately?"}]}]
90:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":["Because its main process finished. A container runs exactly as long as PID 1, so anything that completes, or daemonises itself into the background, ends the container. Check ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"docker ps -a"}]," for the exit code and ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"docker logs <container>"}]," for output. Exit 0 means a clean finish, so the command simply ran and stopped; exit 127 means command not found, usually a typo or a binary missing from the image. For servers, keep the process in the foreground, such as ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"nginx -g "daemon off;""}],"."]}]
91:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":["$","strong",null,{"children":"What is the difference between docker run and docker start?"}]}]
92:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":[["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"docker run"}]," creates a ",["$","strong",null,{"children":"new"}]," container from an image and starts it, so running the same command three times produces three containers. ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"docker start"}]," restarts an existing, stopped container by name or ID, preserving its writable layer and configuration. If ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"docker ps -a"}]," is filling up with stopped containers, that is ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"run"}]," being used where ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"start"}]," was intended, or a missing ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"--rm"}]," flag on one-off commands."]}]
93:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":["$","strong",null,{"children":"How does Docker port mapping work?"}]}]
94:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":[["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"-p hostPort:containerPort"}],". Host on the left, container on the right. So ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"-p 8080:80"}]," makes a container listening on 80 reachable at ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"localhost:8080"}],". Reversing them is extremely common and produces a mapping that silently forwards to a port nothing is listening on. Note that ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"EXPOSE"}]," in a Dockerfile publishes nothing; it only documents the port. Use ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"-p 127.0.0.1:8080:80"}]," to bind to localhost only, since a plain ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"-p"}]," binds all interfaces and Docker's own iptables rules can bypass a host firewall."]}]
95:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":["$","strong",null,{"children":"How do I get a shell inside a Docker container?"}]}]
96:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":[["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"docker exec -it <container> sh"}]," for a running container, using ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"bash"}]," if the image has it. Many slim images do not. If the container has already exited, ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"exec"}]," will not work; start a new one with an overridden command instead: ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"docker run --rm -it --entrypoint sh <image>"}],". That is the standard way to explore an image whose normal command exits immediately or crashes on start."]}]
97:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":["$","strong",null,{"children":"Which restart policy should I use?"}]}]
98:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":[["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"unless-stopped"}]," for most long-running services. It restarts the container after a crash or a Docker daemon restart, but respects a deliberate ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"docker stop"}],", whereas ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"always"}]," will bring back a container you intentionally stopped the next time the daemon starts, which is rarely wanted. Use ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"on-failure"}]," for batch work that should retry on error but not after a successful run, and leave the default ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"no"}]," for one-off commands."]}]
99:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":["$","strong",null,{"children":"How do I remove all stopped Docker containers?"}]}]
9a:["$","p",null,{"className":"text-base sm:text-lg text-slate-700 leading-relaxed mb-4","children":[["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"docker container prune"}]," removes every stopped container in one command and reports how much space was reclaimed. To remove absolutely everything including running containers, ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"docker ps -aq | xargs docker rm -f"}]," works but should be used carefully. The better habit is prevention: add ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"--rm"}]," to any one-off ",["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"docker run"}]," so the container deletes itself on exit, which stops the buildup entirely."]}]
9b:["$","tr",null,{"children":[["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":"Port mappings"}],["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"docker port <n>"}]}]]}]
9c:["$","tr",null,{"children":[["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":"Live resource usage"}],["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"docker stats"}]}]]}]
9d:["$","tr",null,{"children":[["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":"Stop gracefully"}],["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"docker stop <n>"}]}]]}]
9e:["$","tr",null,{"children":[["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":"Remove stopped containers"}],["$","td",null,{"className":"px-3 sm:px-6 py-2 sm:py-4 text-xs sm:text-sm text-slate-700 border-t border-slate-200","children":["$","code",null,{"className":"bg-slate-100 text-rose-600 px-1 sm:px-1.5 py-0.5 rounded font-mono text-sm sm:text-base break-words","children":"docker container prune"}]}]]}]
28:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
26:null
2a:[["$","title","0",{"children":"Docker Containers: Lifecycle, Ports and Debugging"}],["$","meta","1",{"name":"description","content":"Learn how Docker containers work, from docker run and the container lifecycle to port publishing, restart policies and why a container exits immediately."}],["$","link","2",{"rel":"author","href":"https://devopslesson.com"}],["$","meta","3",{"name":"author","content":"DevOpsLesson Team"}],["$","meta","4",{"name":"keywords","content":"docker containers, container lifecycle, docker run, container management, docker networking, docker container exits immediately, docker run vs start, docker port mapping, docker restart policy, docker exec into running container, docker container logs, remove all docker containers, docker stop vs kill, docker tutorial, learn docker, docker for beginners, docker containers, dockerfile tutorial, docker compose tutorial, docker images, docker networking, docker volumes, containerization tutorial, docker beginner guide"}],["$","meta","5",{"name":"creator","content":"DevOpsLesson"}],["$","meta","6",{"name":"publisher","content":"DevOpsLesson"}],["$","meta","7",{"name":"robots","content":"index, follow"}],["$","meta","8",{"name":"googlebot","content":"index, follow, max-video-preview:-1, max-image-preview:large, max-snippet:-1"}],["$","link","9",{"rel":"canonical","href":"https://devopslesson.com/tutorials/docker/docker-containers"}],["$","meta","10",{"name":"google-site-verification","content":"v1hGowZeYvsOScm3hhGtqYv7ZG_yiFG-YbjDK"}],["$","meta","11",{"property":"og:title","content":"Docker Containers"}],["$","meta","12",{"property":"og:description","content":"Learn how Docker containers work, from docker run and the container lifecycle to port publishing, restart policies and why a container exits immediately."}],["$","meta","13",{"property":"og:url","content":"https://devopslesson.com/tutorials/docker/docker-containers"}],["$","meta","14",{"property":"og:site_name","content":"DevOpsLesson"}],["$","meta","15",{"property":"og:image","content":"https://devopslesson.com/api/og?title=Docker+Containers&eyebrow=Docker+Tutorial"}],["$","meta","16",{"property":"og:image:width","content":"1200"}],["$","meta","17",{"property":"og:image:height","content":"630"}],["$","meta","18",{"property":"og:image:alt","content":"Docker Containers"}],["$","meta","19",{"property":"og:type","content":"article"}],["$","meta","20",{"property":"article:published_time","content":"2026-02-04"}],["$","meta","21",{"property":"article:modified_time","content":"2026-09-16"}],["$","meta","22",{"property":"article:author","content":"Vishvesh Patel"}],["$","meta","23",{"property":"article:tag","content":"docker containers"}],["$","meta","24",{"property":"article:tag","content":"container lifecycle"}],["$","meta","25",{"property":"article:tag","content":"docker run"}],["$","meta","26",{"property":"article:tag","content":"container management"}],["$","meta","27",{"property":"article:tag","content":"docker networking"}],["$","meta","28",{"property":"article:tag","content":"docker container exits immediately"}],["$","meta","29",{"property":"article:tag","content":"docker run vs start"}],["$","meta","30",{"property":"article:tag","content":"docker port mapping"}],["$","meta","31",{"property":"article:tag","content":"docker restart policy"}],["$","meta","32",{"property":"article:tag","content":"docker exec into running container"}],["$","meta","33",{"property":"article:tag","content":"docker container logs"}],["$","meta","34",{"property":"article:tag","content":"remove all docker containers"}],["$","meta","35",{"property":"article:tag","content":"docker stop vs kill"}],["$","meta","36",{"name":"twitter:card","content":"summary_large_image"}],["$","meta","37",{"name":"twitter:title","content":"Docker Containers"}],["$","meta","38",{"name":"twitter:description","content":"Learn how Docker containers work, from docker run and the container lifecycle to port publishing, restart policies and why a container exits immediately."}],"$La0","$La1","$La2","$La3","$La4","$La5"]
a6:I[86869,[],"IconMark"]
a0:["$","meta","39",{"name":"twitter:image","content":"https://devopslesson.com/api/og?title=Docker+Containers&eyebrow=Docker+Tutorial"}]
a1:["$","link","40",{"rel":"shortcut icon","href":"/favicon.svg"}]
a2:["$","link","41",{"rel":"icon","href":"/icon","type":"image/png","sizes":"32x32"}]
a3:["$","link","42",{"rel":"icon","href":"/favicon.svg","type":"image/svg+xml"}]
a4:["$","link","43",{"rel":"apple-touch-icon","href":"/icon","sizes":"180x180","type":"image/png"}]
a5:["$","$La6","44",{}
Learn what Docker is and why developers use it. Understand images vs containers, how Docker differs from a virtual machine, the basic workflow and its limits.
Find out why teams use Docker: consistent environments, faster deployment, easier scaling and microservices support, with real-world use cases.
Compare Docker containers and virtual machines on architecture, startup time, isolation and security, and learn when to choose one or use both together.