Docker is a platform for building, packaging, and running applications inside “containers,” lightweight, isolated environments that bundle an application together with every single dependency it genuinely needs to run, all the way down to the exact versions of system libraries involved. This solves a problem nearly every developer has run into at some point, an application that works perfectly on one machine but mysteriously breaks on another, simply because the two machines happened to have slightly different software installed. A Docker container carries its own complete, self contained environment with it, so it behaves identically regardless of where it actually runs, whether that is your own laptop, a colleague’s machine, or a server sitting somewhere in the cloud.
It helps to understand exactly how a container differs from a full virtual machine, since the two are often confused. A virtual machine emulates an entire separate computer, including its own full, independent operating system kernel, which makes it heavy and comparatively slow to start. A container, by contrast, shares the host machine’s own existing kernel, and only isolates the application’s own files, processes, and network view from everything else running on that same machine. This is exactly why containers start in a genuine fraction of a second, rather than the many seconds or even minutes a full virtual machine typically needs to boot.
🧠 Core concepts
Term
Meaning
Image
Read only template/snapshot used to create containers
Container
Running (or stopped) instance of an image
Dockerfile
Text file with instructions to build an image
Registry
Where images are stored (Docker Hub, private registries)
Volume
Persistent storage managed by Docker
Compose
Tool to define/run multi container apps via YAML
Container vs VM
Container shares the host kernel, starts in ms. VM emulates a full OS, starts in seconds/minutes.
📦 Setup
docker --version # installed versiondocker info # daemon + system infodocker version # client + server version
🖼️ Images
docker pull ubuntu # pull latest tagdocker pull ubuntu:22.04 # pull specific tagdocker images # list local imagesdocker image ls # same as abovedocker rmi ubuntu # remove imagedocker rmi -f ubuntu # force removedocker rmi $(docker images -q) # remove ALL imagesdocker image prune # remove dangling (untagged) imagesdocker image prune -a # remove all unused imagesdocker build -t myapp:1.0 . # build from Dockerfile in current dirdocker build -t myapp:1.0 -f Dockerfile.prod . # use specific Dockerfiledocker build --no-cache -t myapp:1.0 . # build ignoring cachedocker tag myapp:1.0 amit/myapp:1.0 # add a new tagdocker login # auth with registrydocker push amit/myapp:1.0 # push imagedocker pull amit/myapp:1.0 # pull imagedocker inspect myapp:1.0 # full metadata (JSON)docker history myapp:1.0 # layer breakdown + sizes
Pin versions
Avoid :latest in real builds. Use explicit tags (node:20.11.1) for reproducibility.
📦 Containers
docker run ubuntu # create + run, exits afterdocker run -it ubuntu bash # interactive shelldocker run -d nginx # detached (background)docker run --name web -d nginx # named containerdocker run -p 8080:80 nginx # host:container port mappingdocker run -v mydata:/app/data nginx # mount named volumedocker run -e "APP_ENV=prod" myapp # env variabledocker run --rm myapp # auto remove on exitdocker run -w /app myapp # set working dirdocker ps # running containersdocker ps -a # all containers (incl. stopped)docker ps -q # container IDs onlydocker stop web # graceful stop (SIGTERM)docker start web # start existing containerdocker restart web # stop + startdocker kill web # force stop (no grace period)docker rm web # remove stopped containerdocker rm -f web # force stop + removedocker rm $(docker ps -aq) # remove ALL containersdocker exec -it web bash # shell into running containerdocker exec web ls /app # run one-off commanddocker logs web # view logsdocker logs -f web # follow logs livedocker top web # processes inside containerdocker stats # live resource usage (all containers)docker inspect web # full metadata (JSON)docker cp file.txt web:/app/ # host to containerdocker cp web:/app/log.txt ./ # container to hostdocker pause web # freeze processesdocker unpause web # resumedocker rename old new # rename containerdocker attach web # attach to main process (risky, see below)
stop vs kill
stop = SIGTERM, graceful, then force-kills after timeout. kill = immediate, no cleanup. Default to stop.
ps shows nothing?
Plain docker ps only shows running containers. Add -a to see stopped/exited ones too.
exec vs attach
Use exec -it ... bash for debugging (separate shell, safe to exit). attach connects to the main process itself; exiting it can kill the container.
📝 Dockerfile instructions
FROM node:20-alpine # base image, always firstWORKDIR /app # sets cwd for everything afterCOPY package*.json ./ # copy deps first (cache layer)RUN npm install # install depsCOPY . . # copy rest of app (changes often)ENV NODE_ENV=production # persists into runtimeARG APP_VERSION=1.0 # build time only, not in final containerEXPOSE 3000 # documentation only, doesn't publish portUSER node # drop to non root userLABEL maintainer="amit@example.com" # metadataCMD ["node", "server.js"] # default command (overridable at runtime)ENTRYPOINT ["node", "server.js"] # fixed command (hard to override)
Instruction
Purpose
FROM
Base image (required, first line)
WORKDIR
Set working directory
COPY
Copy local files into image
ADD
Like COPY, but also handles URLs + auto extracts archives
RUN
Execute command at build time (bakes into layer)
CMD
Default runtime command, overridable
ENTRYPOINT
Fixed runtime command, hard to override
ENV
Env variable, persists to runtime
ARG
Build time only variable
EXPOSE
Documents a port (does NOT publish it)
VOLUME
Declares a mount point
USER
Sets the user to run as
LABEL
Adds metadata
HEALTHCHECK
Defines a container health check
Layer caching order
Copy dependency manifests + install deps BEFORE copying full source. Code changes shouldn’t invalidate the install layer.
COPY vs ADD
Default to COPY. Only use ADD for its two extra tricks: remote URLs and auto-extracting archives.
ENV vs ARG
ARG = build time only, gone at runtime. ENV = persists into the running container.
docker compose up # build + start all servicesdocker compose up -d # detacheddocker compose down # stop + remove containers/networkdocker compose down -v # ALSO removes named volumes (destroys data)docker compose build # build/rebuild imagesdocker compose ps # service statusdocker compose logs # all logsdocker compose logs -f web # follow one service's logsdocker compose exec web bash # shell into a servicedocker compose restart web # restart one servicedocker compose stop # stop without removingdocker compose up -d --scale web=3 # run 3 instances of web
down -v deletes volume data permanently. Think before running it.
docker-compose (hyphen, standalone) is the old tool. docker compose (space, plugin) is current.
docker run --restart no myapp # default, never auto-restartdocker run --restart on-failure myapp # restart only on error exitdocker run --restart always myapp # always restart, even after manual stop + daemon restartdocker run --restart unless-stopped myapp # like always, but respects a manual stop
unless-stopped is usually the right default for long-running services.
🧹 Cleanup
docker system df # disk usage summarydocker system prune # remove stopped containers, unused networks, dangling imagesdocker system prune -a # ALSO remove all unused imagesdocker system prune -a --volumes # ALSO remove unused volumes
-a --volumes is aggressive. Check docker system df first.
🏷️ Flag quick reference
Flag
Meaning
-d
Detached (background)
-it
Interactive + terminal
--name
Container name
-p host:container
Port mapping
-v / --mount
Volume or bind mount
-e KEY=VALUE
Env variable
--rm
Auto remove on exit
--network
Attach to network
--restart
Restart policy
-w
Working directory
🔍 Troubleshooting
Symptom
Fix
Container exits immediately
Check docker logs <container>, main process likely crashed/finished
”Cannot connect to Docker daemon”
Daemon not running, or user lacks permission (sudo usermod -aG docker $USER, relog)
Code changes not showing up
If using COPY (no bind mount), rebuild image. If bind mount, check the mounted path
Build failed partway
docker run -it <last-good-layer-id> bash to inspect state before the failure
⚡ Quick setup example
A minimal end to end flow: build an image, run it, check it, tear it down.
# 1. Create a bare minimum Dockerfilecat > Dockerfile << 'EOF'FROM node:20-alpineWORKDIR /appCOPY package*.json ./RUN npm installCOPY . .EXPOSE 3000CMD ["node", "server.js"]EOF# 2. Build the imagedocker build -t myapp:1.0 .# 3. Run it, mapped to host port 8080, detached, auto-restart, auto-cleandocker run -d --name myapp -p 8080:3000 --restart unless-stopped myapp:1.0# 4. Verify it's up and check logsdocker psdocker logs -f myapp# 5. Tear down when donedocker stop myapp && docker rm myapp
Covers core day to day Docker: images, containers, Dockerfile, networking, volumes, Compose. Orchestration at scale (Kubernetes, Docker Swarm) is a separate topic, worth its own note later.