Skip to main content

Running and Managing Docker Containers — The Complete Lifecycle

Master the complete Docker container lifecycle — creating, running, stopping, restarting, and removing containers with confidence in production.

44 Terms

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.

Bash
+------------------------------------------+
| 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 -> RUNNING
STOPPED -> docker rm -> container deleted

Detailed 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.

Bash
docker 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 background
docker 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 port
docker 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 container
docker 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 exits
docker 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 variables
docker 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 data
docker 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 much
docker run -d \
--name api \
--memory=512m \
--cpus=1.5 \
registry.swiggy.in/order-api:v2.0.0
# Set a restart policy for production services
docker run -d \
--name critical-service \
--restart=unless-stopped \
registry.zerodha.in/trading-engine:v4.1.0
Milestone 2: Managing Running Containers

Once containers are running, you need to monitor and manage them.

Bash
# List all running containers
docker ps
# CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
# a84f9c2b1d3e nginx ... 5m ago Up 5m 80/tcp my-nginx
# List ALL containers including stopped ones
docker ps -a
# Shows running and stopped containers
# List only container IDs (useful for scripting)
docker ps -q
# a84f9c2b1d3e
# b72c8a9f4e1d
# Formatted output — get specific fields
docker 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 containers
docker 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 container
docker stats my-nginx --no-stream
Milestone 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.

Bash
# docker stop — graceful shutdown
docker 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 timeout
docker stop --time 30 my-nginx
# Waits 30 seconds before sending SIGKILL
# docker kill — immediate termination
docker 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 signal
docker kill --signal SIGHUP my-nginx
# Useful for applications that reload config on SIGHUP (like nginx)
# Start a stopped container
docker start my-nginx
# Restart a running container (stop + start)
docker restart my-nginx
docker restart --time 5 my-nginx # 5 second grace period
# Remove a stopped container
docker rm my-nginx
# Remove a running container forcefully
docker rm -f my-nginx
# Equivalent to: docker kill my-nginx && docker rm my-nginx
# Remove all stopped containers
docker 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 -f

The difference between stop and kill in practice at Zerodha:

Bash
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 risk
Milestone 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.

Bash
# Get all logs from a container
docker 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 lines
docker logs --tail 100 my-nginx
# Show logs with timestamps
docker logs --timestamps my-nginx
# 2024-01-15T09:00:00.000000000Z nginx: starting...
# Show logs from a specific time
docker logs --since 2024-01-15T09:00:00 my-nginx
docker logs --since 1h my-nginx # last 1 hour
docker logs --since 30m my-nginx # last 30 minutes
# Show logs until a specific time
docker logs --until 2024-01-15T10:00:00 my-nginx
# Combine filters — logs in a specific time window
docker 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 stopped
docker logs stopped-container-name
# Logs are preserved until the container is removed with docker rm
Milestone 5: Inspecting and Exec-ing into Containers
Bash
# Get complete container metadata as JSON
docker inspect my-nginx
# Returns a huge JSON object with everything:
# NetworkSettings, Mounts, Config, State, HostConfig
# Extract specific fields with format
docker 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 with
docker inspect --format '{{range .Config.Env}}{{println .}}{{end}}' my-nginx
# Run a command inside a running container
docker exec my-nginx ls /etc/nginx
# Lists files in the nginx config directory
# Get an interactive shell inside a running container
docker 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 container
docker exec -it --user root my-nginx bash
# Check processes running inside the container
docker top my-nginx
# UID PID PPID CMD
# root 12345 1 nginx: master process
# www 12346 12345 nginx: worker process
Milestone 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.

Bash
# 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 count
docker 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 exit
docker 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 manually
docker 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 count
docker inspect --format '{{.RestartCount}}' payment-api
# 3 — this container has restarted 3 times
# High restart count = something is wrong with the application

Common 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
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

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

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.

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.

Resources

Explore More in Docker Fundamentals and Container Lifecycle

All 3 Topics

Frequently Asked Questions

Is Running and Managing Docker Containers — The Complete Lifecycle free to learn on DevOps Network?

Yes - this topic, like everything on DevOps Network, is 100% free with no paywall or sign-up gate.

What does the Running and Managing Docker Containers — The Complete Lifecycle topic cover?

Master the complete Docker container lifecycle — creating, running, stopping, restarting, and removing containers with confidence in production.