Overview and What You Will Learn
Every Docker workflow starts with running a container. But there is a big difference between knowing that docker run nginx starts a web server and understanding every flag, every state transition, and every management command well enough to use Docker confidently in production.
In this guide you will learn the complete lifecycle of a Docker container — from the moment it is created to the moment it is removed — and every command that controls that lifecycle. You will understand when to use docker stop versus docker kill, how to read container logs properly, how to inspect a running container, and how to clean up containers without accidentally deleting data you need.
By the end of this guide you will be able to manage Docker containers in production with confidence — running, inspecting, debugging, stopping, and cleaning up — without needing to look up commands.
Why This Matters in Production
At Razorpay, a payment service container that crashes at 2am needs to be diagnosed and fixed quickly. An engineer who knows how to read container logs with the right filters, check resource usage with docker stats, and exec into a running container to verify its state can resolve the issue in minutes. An engineer who only knows docker ps and docker logs will spend an hour guessing.
Understanding container lifecycle also prevents data loss. Removing a container with docker rm deletes its filesystem. If you are storing data inside a container instead of in a volume, that data is gone permanently. Knowing this before it happens prevents a disaster.
Core Principles
A Docker container moves through five distinct states. Understanding these states and what triggers each transition is the foundation of container management.
+------------------------------------------+| CREATED || docker create has been called || Container exists but process not started || No CPU or memory being used |+------------------------------------------+ | docker start | v+------------------------------------------+| RUNNING || Container process is executing || Using CPU, memory, network || docker ps shows this container |+------------------------------------------+ | | docker pause docker stop/kill | | v v+------------------+ +------------------+| PAUSED | | STOPPED || Process frozen | | Process exited || SIGSTOP sent | | SIGTERM or kill || Memory preserved | | No CPU used |+------------------+ +------------------+ docker unpause | v RUNNING STOPPED -> docker start -> RUNNINGSTOPPED -> docker rm -> container deletedDetailed Step-by-Step Practical Lab
Milestone 1: The docker run Command — Every Important Flag
docker run is the most important Docker command. It creates and starts a container in one step. Every flag changes a fundamental aspect of how the container runs.
# The minimal form — just run nginxdocker run nginx# This runs in the FOREGROUND — your terminal is attached# Ctrl+C stops the container# Not useful for most real work # Run in detached mode — container runs in backgrounddocker run -d nginx# Returns: a84f9c2b1d3e... (container ID)# Your terminal is free immediately # Give the container a name (otherwise Docker generates a random one)docker run -d --name my-nginx nginx# Now you can reference it by name: docker stop my-nginx # Map a host port to a container portdocker run -d --name web -p 8080:80 nginx# localhost:8080 on the host -> port 80 inside container# Open http://localhost:8080 in your browser to verify # Run interactively — get a shell inside the containerdocker run -it ubuntu bash# -i = keep stdin open (interactive)# -t = allocate a pseudo-TTY (gives you a proper terminal)# You are now inside the container — exit with 'exit' or Ctrl+D # Run a one-off command and remove the container after it exitsdocker run --rm alpine echo "hello from alpine"# Container starts, prints "hello from alpine", then is deleted# --rm is useful for one-off tasks where you do not want cleanup work # Set environment variablesdocker run -d \ --name payment-api \ -e NODE_ENV=production \ -e DB_HOST=10.0.1.50 \ -e DB_PORT=5432 \ registry.razorpay.in/payment-api:v3.1.0 # Mount a volume for persistent datadocker run -d \ --name postgres \ -e POSTGRES_PASSWORD=secret123 \ -v postgres-data:/var/lib/postgresql/data \ postgres:15# postgres-data is a named volume — data persists after container removal # Set resource limits to prevent container from consuming too muchdocker run -d \ --name api \ --memory=512m \ --cpus=1.5 \ registry.swiggy.in/order-api:v2.0.0 # Set a restart policy for production servicesdocker run -d \ --name critical-service \ --restart=unless-stopped \ registry.zerodha.in/trading-engine:v4.1.0Milestone 2: Managing Running Containers
Once containers are running, you need to monitor and manage them.
# List all running containersdocker ps# CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES# a84f9c2b1d3e nginx ... 5m ago Up 5m 80/tcp my-nginx # List ALL containers including stopped onesdocker ps -a# Shows running and stopped containers # List only container IDs (useful for scripting)docker ps -q# a84f9c2b1d3e# b72c8a9f4e1d # Formatted output — get specific fieldsdocker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"# NAMES STATUS PORTS# my-nginx Up 5 mins 0.0.0.0:8080->80/tcp# postgres Up 12 mins 5432/tcp # Live resource usage for all running containersdocker stats# CONTAINER CPU % MEM USAGE/LIMIT MEM % NET I/O BLOCK I/O# my-nginx 0.0% 6.2MiB/1GiB 0.6% 1kB/0B 0B/0B# postgres 0.2% 32MiB/1GiB 3.1% 2kB/1kB 8MB/0B # One-shot stats (no live update — useful in scripts)docker stats --no-stream # Stats for a specific containerdocker stats my-nginx --no-streamMilestone 3: Stopping and Removing Containers
docker stop and docker kill both stop a container but they do it differently. The difference matters for data integrity.
# docker stop — graceful shutdowndocker stop my-nginx# Sends SIGTERM to the container process# Waits 10 seconds for it to exit cleanly# If it does not exit in 10 seconds, sends SIGKILL# Applications that handle SIGTERM can flush buffers and close connections # Change the grace period timeoutdocker stop --time 30 my-nginx# Waits 30 seconds before sending SIGKILL # docker kill — immediate terminationdocker kill my-nginx# Sends SIGKILL immediately — no grace period# Use only when a container is frozen and not responding to stop# Data that was not flushed to disk is lost # Send a specific signaldocker kill --signal SIGHUP my-nginx# Useful for applications that reload config on SIGHUP (like nginx) # Start a stopped containerdocker start my-nginx # Restart a running container (stop + start)docker restart my-nginxdocker restart --time 5 my-nginx # 5 second grace period # Remove a stopped containerdocker rm my-nginx # Remove a running container forcefullydocker rm -f my-nginx# Equivalent to: docker kill my-nginx && docker rm my-nginx # Remove all stopped containersdocker container prune# WARNING: This will remove all stopped containers# Are you sure you want to continue? [y/N] # Remove all stopped containers without confirmation (for CI scripts)docker container prune -fThe difference between stop and kill in practice at Zerodha:
Trading engine container: docker stop → sends SIGTERM Trading engine catches SIGTERM: - Closes open trades safely - Flushes order book to disk - Closes database connections cleanly - Exits with code 0 Total: 3 seconds, no data loss Without SIGTERM handling: docker kill → sends SIGKILL immediately Kernel kills process instantly Order book state in memory is lost Database connections left in broken state Data corruption riskMilestone 4: Reading Container Logs
Docker captures everything a container writes to stdout and stderr and stores it locally. The docker logs command reads these stored logs.
# Get all logs from a containerdocker logs my-nginx # Follow logs in real time (like tail -f)docker logs -f my-nginx# Ctrl+C to stop following # Show only the last N linesdocker logs --tail 100 my-nginx # Show logs with timestampsdocker logs --timestamps my-nginx# 2024-01-15T09:00:00.000000000Z nginx: starting... # Show logs from a specific timedocker logs --since 2024-01-15T09:00:00 my-nginxdocker logs --since 1h my-nginx # last 1 hourdocker logs --since 30m my-nginx # last 30 minutes # Show logs until a specific timedocker logs --until 2024-01-15T10:00:00 my-nginx # Combine filters — logs in a specific time windowdocker logs --since 2h --until 1h my-nginx # Show stderr only (errors)docker logs my-nginx 2>&1 | grep -i error # Get logs for a container that has already stoppeddocker logs stopped-container-name# Logs are preserved until the container is removed with docker rmMilestone 5: Inspecting and Exec-ing into Containers
# Get complete container metadata as JSONdocker inspect my-nginx# Returns a huge JSON object with everything:# NetworkSettings, Mounts, Config, State, HostConfig # Extract specific fields with formatdocker inspect --format '{{.NetworkSettings.IPAddress}}' my-nginx# 172.17.0.2 docker inspect --format '{{.State.Status}}' my-nginx# running docker inspect --format '{{.HostConfig.RestartPolicy.Name}}' my-nginx# unless-stopped # See all environment variables a container was started withdocker inspect --format '{{range .Config.Env}}{{println .}}{{end}}' my-nginx # Run a command inside a running containerdocker exec my-nginx ls /etc/nginx# Lists files in the nginx config directory # Get an interactive shell inside a running containerdocker exec -it my-nginx bash# Now you are inside the container# Changes made here are lost when the container stops# Use this for debugging — not for making permanent changes # Run as a specific user inside the containerdocker exec -it --user root my-nginx bash # Check processes running inside the containerdocker top my-nginx# UID PID PPID CMD# root 12345 1 nginx: master process# www 12346 12345 nginx: worker processMilestone 6: Restart Policies
Restart policies determine what Docker does when a container exits. Choosing the right policy prevents both unnecessary restarts and missing automatic recovery.
# No restart policy — container stays stopped (default)docker run -d --restart=no nginx# When nginx crashes, it stays stopped# You have to restart it manually # Restart only on failure (non-zero exit code)docker run -d --restart=on-failure payment-api# If the API crashes with exit code 1, Docker restarts it# If you intentionally stop it (exit code 0), it stays stopped# Good for: application services where intentional stop should stick # Set a maximum retry countdocker run -d --restart=on-failure:5 payment-api# Restart up to 5 times, then give up# Prevents restart loops from hammering the host # Always restart — even on clean exitdocker run -d --restart=always nginx# Restarts no matter how the container exits# Survives Docker daemon restart (container starts on system boot)# Good for: web servers, databases on single-host setups # Unless stopped manuallydocker run -d --restart=unless-stopped trading-engine# Like always, but respects manual docker stop# Survives daemon restart# If you run docker stop, it stays stopped# Good for: most production services # Check restart countdocker inspect --format '{{.RestartCount}}' payment-api# 3 — this container has restarted 3 times# High restart count = something is wrong with the applicationCommon Mistakes
| Mistake | What Happens | Fix |
|---|---|---|
Using docker kill when docker stop works |
Data loss in apps that flush on SIGTERM | Default to docker stop, use kill only for frozen containers |
Not using --rm for one-off containers |
Hundreds of stopped containers accumulate | Add --rm to any docker run that is a one-off task |
Running docker rm -f on a container with volumes |
Container gone but data behaviour unclear | Always check docker inspect for mounts before force-removing |
Using --restart=always for batch jobs |
Job re-runs forever even after successful completion | Use --restart=on-failure for jobs, --restart=unless-stopped for services |
Reading logs without --since on a long-running container |
Gets gigabytes of logs scrolling past | Always use --tail 100 or --since 1h to scope your query |
| Not naming containers | Cannot reference containers by name | Always use --name for long-running services |
Troubleshooting Reference
| Problem | Symptom | Command | What to Look For |
|---|---|---|---|
| Container keeps restarting | Restarting (1) in docker ps |
docker logs --tail 50 name |
Application error causing crash |
| Container exits immediately | Status shows Exited (0) |
docker logs name |
App runs to completion — not a server |
| Container exits with error | Status shows Exited (1) |
docker logs name |
Application error message |
| Cannot exec into container | Error: container not running |
docker ps -a |
Container has stopped — check logs |
| Port already in use | Bind for 0.0.0.0:8080 failed |
`ss -tulpn | grep 8080` |
| Container using too much memory | Slow host, other containers affected | docker stats |
Memory usage approaching limit |
PLACEMENT PRO TIP**Tip:** Always name your containers with `--name` when running them for development or production. `docker logs payment-api` is infinitely more readable than `docker logs a84f9c2b1d3e`. Container names are also the hostname other containers use to find them on a user-defined network.
REMEMBER THIS**Remember:** `docker rm` permanently deletes a container's filesystem. Any files written inside the container — logs, uploads, database files — that are not in a volume are gone forever. Before removing a container, run `docker inspect --format '{{range .Mounts}}{{.Source}}:{{.Destination}}{{end}}' container-name` to see what volumes it has.
COMMON MISTAKE / WARNING**Common Mistake:** Using `--restart=always` for a container that has an application bug causing it to crash immediately. Docker will restart it thousands of times per hour, consuming CPU and filling logs. Always check the restart count with `docker inspect --format '{{.RestartCount}}'` — anything above 5 means something is wrong with the application, not with Docker.
COMMON MISTAKE / WARNING**Security:** `docker exec -it container bash` gives you a shell with the permissions of the user the container runs as. If the container runs as root (which it does by default), you have root access to the container filesystem. This is useful for debugging but should be an explicit, audited action on production containers — not a routine habit.