Running a Container in Docker
A friendly, step-by-step tour of what actually happens the moment you type "docker run": the options you will use every day, how a container lives and dies, and how to run one safely and reliably.
Building an image is only half the story. An image sitting on disk does nothing by itself, just like a recipe card does nothing until someone actually cooks the meal. The moment of truth is running that image: turning a frozen, read-only blueprint into a live, breathing process that serves real traffic, processes real data, and can crash, restart, or scale exactly like any other program on a computer.
This guide focuses entirely on that moment and everything that follows it. You will learn what the docker run command actually does behind the scenes, the handful of options you will genuinely use in daily work, how a running container stays alive and eventually dies, and how to keep it fast, safe and easy to monitor. Every new term is explained the first time it appears, so no earlier Docker knowledge is assumed.
1What Does “Running a Container” Mean?
Before touching any command, it helps to be precise about what “running” actually creates.
An image is a read-only package containing an application and everything it needs to work: its code, its libraries, and a minimal slice of an operating system’s files. It never changes and never does anything on its own; it simply sits in storage, waiting. A container is what you get the moment the Docker Engine takes that image, adds one thin writable layer on top of it, and starts a real operating-system process from it. That process has its own private view of the file system, its own network address, and its own list of running programs, even though it is, underneath, an ordinary process on the host machine.
“Running a container” therefore means asking the Docker Engine to perform four things in order: make sure the image exists locally, prepare an isolated environment for it using Linux kernel features, start the image’s main program inside that environment, and then keep track of that process until it stops. All of this typically begins with one line typed into a terminal.
An image is like a sealed, pre-packed meal kit sitting in a freezer, complete with every ingredient measured out. Running a container is the act of taking that kit out, unwrapping it, and actually cooking it on a stove. The freezer can hold the exact same kit forever without it doing anything; only the act of cooking turns it into a hot meal that can be eaten, spoiled, or thrown away. You can cook the same kit a hundred times and get a hundred separate meals, and burning one meal never touches the sealed kits still in the freezer.
The simplest possible example
Typing docker run hello-world is enough to see the whole idea in miniature. Docker checks whether an image named hello-world already exists on the machine; if not, it downloads a tiny one from a public registry, starts a container from it, lets that container print a short friendly message to the screen, and then lets the container exit on its own because its job was only to print that one message. Nothing is left running afterward, yet a real container genuinely existed, however briefly.
It is worth pausing on why this single moment gets so much attention throughout this guide. Building an image is usually done once, then reused many times, but running that image happens over and over, on different machines, under different real-world conditions, sometimes thousands of times a day for a busy service. Every one of the small decisions made at that moment, whether a port is published, whether a volume is attached, whether a restart policy is set, is what actually determines how that particular running copy of the application behaves once real users, real traffic, or a real failure eventually reach it.
Docker actually has two related commands that beginners often confuse. docker run creates a brand-new container from an image and starts it in one step. docker start restarts a container that already exists but is currently stopped, reusing the same container rather than creating a new one. Reaching for run every time, even for a container you already created once, quietly produces a pile of duplicate containers instead of reusing the one you meant to restart.
2Core Concepts Behind “docker run”
A handful of words appear constantly around running containers. Getting comfortable with them now makes every later chapter easier.
docker run
The command that creates a container from an image and starts its main process, all in one step, applying whatever options you supply.
Container name and ID
Every container gets a long random ID automatically, and can also be given a short, memorable name so later commands are easier to type correctly.
Foreground vs detached
Foreground keeps the container’s output attached to your terminal until it stops. Detached mode starts it in the background and immediately returns control to you.
Main process (PID 1)
The one command every container is built to run first. When that process exits, the whole container is considered stopped, no matter what else may still be happening.
Published port
A rule that forwards traffic arriving at a port on the host machine into a specific port inside the running container.
Mounted volume
A storage location attached to the container at run time so that data written there survives even after the container itself is removed.
Environment variable
A named piece of configuration passed into the container when it starts, such as a database address, without baking that value into the image itself.
Restart policy
A rule telling the engine whether, and how, to automatically start the container again if it stops or if the host machine reboots.
Reading a real command, piece by piece
A typical real-world command looks like this in spirit: run an image, give the container a name, publish a port, attach a volume, pass in an environment variable, set a restart policy, and run it in the background. Every one of those pieces is an independent option that can be mixed and matched, and the order mostly does not matter as long as the image name comes last. The rest of this guide walks through exactly what each of these pieces does underneath, one at a time.
Always give long-lived containers an explicit, readable name with the naming option instead of leaving Docker to generate a random one. It makes every later command, such as viewing logs or stopping the container, far less error-prone, especially once several containers are running at once.
Interactive versus non-interactive containers
Not every container is meant to run quietly in the background forever. Some are started specifically so a person can type commands into them directly, such as opening a temporary shell inside a fresh Linux image to experiment safely without touching the host machine at all. Running a container this way usually combines two separate options: one that keeps an input channel open so you can type into it, and another that allocates a proper interactive terminal so text formatting, colours and cursor movement behave the way they would in a normal terminal window. Without both together, the container either ignores everything you type or displays it in a broken, unreadable way. This interactive style is common for quick exploration and debugging, while detached mode, covered later in this guide, is what almost every long-running service actually uses in practice.
3Architecture: What Handles Your Command
Typing one line hides a short relay race between several small, specialised programs.
flowchart LR
CLI["Docker CLI: the docker run command"] -->|"REST API call"| API["Engine API"]
API --> D["dockerd: the Docker daemon"]
D -->|"checks local cache, pulls if missing"| REG[("Registry: Docker Hub or Amazon ECR")]
D -->|"gRPC"| C["containerd: container supervisor"]
C --> SH["containerd-shim: one per container"]
SH --> R["runc: OCI runtime"]
R --> P["Your application process"]
The Docker CLI, the docker command itself, does very little real work. It simply turns your typed options into a structured request and sends it to the Engine API, a set of agreed messages the rest of the system understands. That request usually travels over a local Unix socket, a special file used for programs on the same machine to talk to each other quickly and privately.
The daemon, whose process name is dockerd, receives the request and acts as the manager. Its first job is to check whether the image you asked for already exists locally; if not, it downloads, or pulls, the missing layers from a registry such as Docker Hub. Once the image is available, the daemon does not start the container itself. It delegates that job to containerd, an independent supervisor program that Kubernetes also uses directly. containerd, in turn, calls runc, a small program that follows the Open Container Initiative standard and does the actual low-level work of asking the Linux kernel to build an isolated space and launch your process inside it.
Once your process is running, runc’s job is finished and it exits, but a lightweight helper called the shim stays behind for as long as the container lives. The shim keeps the container’s input and output connected, and remembers its exit code the moment it stops, so that later commands like viewing logs still work correctly.
Think of ordering food through a restaurant’s app. You are the Docker CLI, placing the order. The app’s servers are the Engine API. The restaurant manager, who checks whether ingredients are in stock and reorders them if not, is the daemon. The kitchen supervisor who assigns your order to a station is containerd. And the line cook who actually prepares your dish, then steps away once it is plated, is runc. A runner stays by your table afterward to make sure everything keeps going smoothly, just like the shim.
Why the chain is split into so many small pieces
It might seem simpler if one single program handled everything from receiving your command to running your process, and early container tools were sometimes built closer to that shape. Splitting the work across the daemon, containerd, the shim and runc instead brings two practical benefits that matter once a machine is running many containers at once, not just one. First, if the daemon itself needs to be restarted, perhaps to install an upgrade, the shim keeps every already-running container’s process alive and connected in the meantime, so a routine engine upgrade does not force every container on that machine to stop and restart along with it. Second, because each piece follows the same open Open Container Initiative standard at its boundary, other tools, including Kubernetes itself, can talk to containerd and runc directly without needing the Docker daemon at all, which is exactly why images built and tested using the Docker Engine continue to run correctly under those other systems.
4Internal Working: Building the Isolated Space
The instant before your application code runs, the kernel quietly builds the walls that make a container feel like its own machine.
When runc is asked to start a container, it uses two ordinary Linux kernel features to construct the isolation everyone associates with containers. The first is the namespace, which gives the new process its own private view of one type of system resource. A process ID namespace makes the container’s first process appear as process number one from the inside, even though the host sees it under a completely different, much larger number. A network namespace gives the container its own network interface and IP address. A mount namespace gives it its own view of the file system, hiding the host’s real files unless they are deliberately shared in.
The second feature is the control group, or cgroup, which limits and measures how much processor time, memory and disk activity that process is allowed to use. If a memory limit is set and the container’s process tries to use more than its share, the kernel intervenes rather than letting it starve every other process on the machine.
flowchart TB
Req["runc receives the start request"] --> NS["Create namespaces: PID, network, mount, hostname"]
NS --> CG["Apply cgroup limits: CPU, memory, disk I/O"]
CG --> FS["Assemble the file system from image layers plus one writable layer"]
FS --> EXEC["Execute the container's main process inside this isolated space"]
Only after namespaces are created, cgroup limits are attached, and the file system is assembled from the image’s layers plus a fresh writable layer on top, does runc finally execute the container’s main command. From that point on, to the application itself, the machine appears to be entirely its own: its own process list, its own network card, its own root file system, even though every one of those things is, underneath, a carefully fenced-off slice of the same host the other containers are also running on.
A running container is not a miniature virtual machine. It shares the host’s actual kernel with every other container on that machine. This is exactly why it starts in a fraction of a second instead of tens of seconds, and also why the isolation, while normally strong enough for everyday use, is not identical to the harder boundary a virtual machine provides.
5Data Flow and the Container Lifecycle
A container is not simply “on” or “off.” It moves through several distinct states, and knowing them prevents a lot of confusion.
sequenceDiagram
participant U as You
participant CLI as Docker CLI
participant D as dockerd
participant C as containerd + runc
U->>CLI: docker run -d --name web myapp
CLI->>D: create and start container
D->>D: pull image if not present locally
D->>C: create container, then start it
C-->>D: process running, PID assigned
D-->>CLI: container ID returned
CLI-->>U: prints container ID, returns control
Note over C: container now in Running state
The five states, one by one
Created
The container’s file system and configuration are prepared, but its main process has not been started yet. This state is brief and rarely seen directly when using docker run, since run both creates and starts in one step.
Running
The main process is active and doing real work. This is the state a healthy, long-lived container spends almost all of its life in.
Paused
Every process inside the container is frozen in place using a cgroup freezer, without being terminated. Rarely used outside of specific debugging or maintenance scenarios.
Stopped (exited)
The main process has ended, either because it finished its work, crashed, or received a stop signal. Its exit code is recorded and can be inspected afterward.
Removed
The container’s writable layer and metadata are permanently deleted. Anything the application wrote that was not saved to a volume is now gone for good.
Stopping a container the polite way
When you ask Docker to stop a running container, it does not simply kill the process instantly. It first sends a termination signal, giving the application a short grace period, typically ten seconds by default, to save its work, close open connections, and shut itself down cleanly. Only if the process ignores this signal and the grace period expires does Docker forcibly kill it. An application that never listens for this polite request loses in-flight work on every single stop, restart, or deployment, which is why handling it correctly is considered a basic requirement for anything meant to run in production.
Stopping asks nicely and waits. Killing skips the grace period and ends the process immediately, useful when a container is completely unresponsive. Removing deletes a container’s writable layer entirely and only works on a container that is already stopped, unless you explicitly force it, which also stops it first.
6The Run Options You Will Actually Use
Docker’s run command supports dozens of options, but a small handful cover almost every real situation you will meet.
Detached mode
Starts the container in the background and immediately hands control of the terminal back to you, essential for anything meant to keep running unattended.
Container name
Gives the container a fixed, human-readable name instead of a randomly generated one, so later commands are simple and unambiguous.
Publish a port
Maps a port on the host machine to a port inside the container, letting outside traffic reach an internal service such as a web server.
Mount a volume
Attaches a managed storage location, or a specific folder from the host, into the container’s file system so data can outlive the container.
Environment variable
Passes a piece of runtime configuration, such as a database password or a feature flag, into the container without editing the image.
Restart policy
Tells the engine to automatically restart the container if it stops unexpectedly, or when the whole host machine reboots.
Auto-remove
Automatically deletes the container the instant it stops, handy for short-lived, one-off tasks that should leave nothing behind.
Memory and CPU limits
Caps how much of the host’s memory and processor time this one container is allowed to consume, protecting its neighbours from a runaway process.
Restart policies compared
| Policy | Behaviour | Typical use |
|---|---|---|
| no | Never restart automatically. This is the default if nothing is specified. | One-off jobs and quick manual tests |
| on-failure | Restart only if the container exits with an error code, optionally up to a maximum number of attempts. | Batch jobs that should retry after a genuine failure |
| always | Always restart, no matter how the container stopped, including after the host reboots. | Long-running services such as web servers or databases |
| unless-stopped | Behaves like always, except it will not restart automatically if a person deliberately stopped it. | Most production services; respects a manual stop |
Running an important service without any restart policy at all means that if it crashes at three in the morning, or the server reboots for a routine security patch, nothing brings it back automatically. For anything meant to stay available, “unless-stopped” or “always” is almost always the right default, not the engine’s own default of “no restart.”
Labels: attaching your own metadata
Beyond the options above, Docker also lets you attach arbitrary labels to a container at run time, small key-and-value notes such as recording which team owns it, which environment it belongs to, or which version of an internal deployment tool created it. Labels do nothing to the running application itself; they exist purely so that other tools, and other people, can later filter, group or organise containers by these notes, for example listing every container labelled as belonging to a particular project across a busy shared server. Many monitoring and orchestration tools lean heavily on labels behind the scenes to automatically discover and organise the containers they manage, so developing the habit of labelling containers consistently pays off well beyond a single afternoon of manual commands.
7Networking a Running Container
A container’s own private network address is invisible to the outside world until you deliberately open a door to it.
By default, a newly run container is attached to a private virtual network called bridge and is handed its own internal IP address, similar to a home router assigning addresses to phones and laptops. Containers on the same network can reach each other using that internal address, and on a user-created network, they can also find each other simply by container name, because the engine runs a small internal DNS service for exactly this purpose.
That internal address, however, is not reachable from outside the host machine on its own. To let outside traffic in, you publish a port at run time, for example telling Docker that anything arriving at port 8080 on the host should be forwarded to port 80 inside the container. Behind the scenes, this quietly configures the host’s own firewall rules to perform that redirection automatically, for as long as the container is running.
flowchart LR
Ext["Outside request to host:8080"] --> FW["Host firewall / NAT rule"]
FW --> Br["Bridge network"]
Br --> Ctr["Container's internal port 80"]
Ctr2["A second container"] -->|"talks by container name via internal DNS"| Ctr
Br -.-> Ctr2
The bridge network is like the internal phone extensions inside an office building, where any desk can dial any other desk directly. Publishing a port is like assigning extension 205 a public phone number so a customer calling from outside can reach that desk without ever knowing the internal wiring exists.
Binding to a specific host address
By default, publishing a port makes it reachable on every network interface the host machine has, which on a server with more than one network card, or one connected to both a private internal network and the public internet, can quietly expose a service further than intended. A run-time option lets you bind a published port to one specific host address instead, for example only the machine’s internal, private network interface, so the same container remains reachable to trusted internal systems while staying completely invisible from the public internet. This one small detail is a common cause of unpleasant surprises during a security review, when a service everyone assumed was internal-only turns out to have quietly been reachable from outside all along, simply because a port was published without specifying which interface it should actually listen on.
Choosing a network mode when you run
Bridge (the default)
- Good isolation between the container and the host’s own network
- Works well for almost all single-host situations
- Requires explicitly publishing any port you want reachable from outside
Host network
- Shares the host’s network stack directly, skipping translation for lower latency
- No isolation between the container’s ports and the host’s own ports
- Best reserved for cases where every extra fraction of a millisecond genuinely matters
8Persisting Data While a Container Runs
By default, everything a container writes disappears the moment it is removed. Volumes are how you decide, on purpose, what should survive.
Every running container has its own thin, writable layer sitting on top of its image’s read-only layers. Anything the application writes there, unless told otherwise, lives only inside that one container. The moment the container is removed, that writable layer, and everything written to it, is deleted permanently along with it.
A volume is a storage location managed by the Docker Engine but kept independent of any single container’s writable layer. When you mount a volume into a container at run time, anything the application writes to that mounted location is really being saved into the volume, which continues to exist, fully intact, even after the container that used it is stopped, removed, or replaced entirely by a newer version built from an updated image.
Named volume
Fully managed by Docker itself. The safest and simplest default for a database’s files or any data you must never lose.
Bind mount
Maps a specific existing folder from the host straight into the container, commonly used so code edited on your laptop appears instantly inside a running container.
tmpfs mount
Stores data only in the host’s memory, never written to disk at all. Extremely fast, but every byte disappears the instant the container stops.
Running a database container without mounting a volume for it feels fine at first, because everything appears to work. The failure only shows up later, the first time that container is removed to apply an update, at which point every row ever inserted is gone without warning. Attaching a volume the very first time you run a stateful service costs nothing and prevents this permanently.
Read-only mounts and shared mounts
A mounted volume or bind mount does not have to be writable at all. Marking a mount as read-only at run time lets a container read configuration files, reference data, or shared assets from that location while being completely unable to change or delete them, which is a simple and effective safeguard for any data a particular container should only ever consume rather than modify. The same physical volume can also be mounted, at the same time, into several different containers, some with write access and some marked read-only, which is exactly how a pattern like one container writing log files while a separate, more restricted container only reads and forwards them tends to be built in practice.
9Resource Limits and Running Modes
Two easily overlooked choices, how much of the machine a container may use and whether it runs attached to your terminal, quietly shape how reliable it is.
Without an explicit limit, a single container is free to use as much of the host’s memory and processor time as it can get. On a shared machine, one misbehaving container, perhaps stuck in a loop or leaking memory, can starve every other container running alongside it, or even crash the entire host. Setting a memory limit and a CPU limit when you run a container hands that container a fixed, guaranteed slice of the machine and, just as importantly, protects everyone else on that machine from it.
Separately, every container runs in one of two modes. Foreground mode keeps the container’s output streaming directly into your terminal and ties its life to that terminal session; closing the terminal, or pressing the interrupt key combination, normally stops the container too. Detached mode starts the container fully in the background and instantly returns your terminal to you, which is the mode almost every real service, anything meant to keep running after you walk away from your keyboard, actually needs.
| Situation | Recommended mode |
|---|---|
| Quickly testing a command or exploring an image | Foreground, so you see the output immediately |
| A web server, database, or any long-running service | Detached, combined with a restart policy |
| A one-off script that should clean up after itself | Foreground with the auto-remove option |
Even in detached mode, you are never locked out. A separate command lets you attach an interactive shell inside an already-running container to look around, check a configuration file, or debug a problem, without stopping the main application process at all.
10Advantages, Disadvantages and Trade-offs
Running everything in containers is not automatically the right answer for every situation. Weighing the honest trade-offs makes for better decisions.
Advantages of running this way
- The exact same image runs identically on a laptop, a test server, and production, removing an entire class of environment-related bugs
- A container starts in well under a second, making it easy to launch, replace, or scale copies quickly
- Options like restart policies and resource limits give fine control over reliability without touching the application’s own code
- Multiple isolated services can share one physical machine efficiently, since no separate operating system is needed for each
Disadvantages and honest limits
- Forgetting a volume, a restart policy, or a resource limit at run time is easy, and each mistake causes a different kind of pain later
- Isolation is weaker than a full virtual machine, since the kernel underneath is shared with every other container
- Running dozens of containers by hand with individual commands becomes unmanageable quickly, which is exactly the gap an orchestrator is built to fill
- Some run-time options, especially networking modes and privilege flags, can accidentally weaken security if used without understanding what they remove
11Security While Running a Container
Most of the flags available on docker run exist to make a container either safer or more dangerous. Knowing which is which matters.
Run as non-root
Telling the container to run its process as an ordinary, unprivileged user rather than root sharply limits what damage a compromised application can do inside the container.
Read-only file system
Marking the container’s root file system as read-only at run time, with only specific folders writable, stops many attacks that rely on writing new files anywhere they please.
Drop Linux capabilities
Explicitly removing powerful low-level permissions the application does not actually need shrinks what a break-in could exploit, even before considering the application’s own bugs.
Avoid –privileged
This flag removes almost all of the isolation this guide describes, giving the container nearly the same access as a process running directly on the host.
The situation
Every run-time option that grants a container more access, more capabilities, a shared network namespace, a mounted host socket, also grants more access to anything that manages to compromise the application running inside it.
The response
Start every container with the fewest privileges that let it actually do its job: a non-root user, a read-only file system where practical, only the specific capabilities it genuinely needs, and no privileged flag unless a very specific, trusted tool truly requires it.
Why it works
An attacker who compromises the application inside the container still hits the same walls every other unprivileged process on the host would hit, rather than inheriting a shortcut past them.
Mounting the host’s own Docker socket into a container so that container can, in turn, run more containers is a surprisingly common pattern, but it effectively hands that container control over every other container on the host, since anything with access to that socket can talk to the daemon directly. Treat it as a special case requiring strong justification, never a routine convenience.
Trusting what you run
Security at run time also depends heavily on decisions made long before the run command is ever typed: which image was chosen in the first place. Pulling and running an image from an unfamiliar source, without checking who published it or whether it has been scanned for known vulnerabilities, means trusting a stranger’s software with real access to your machine’s kernel through the shared namespaces and cgroups described earlier in this guide. Preferring official, actively maintained images, verifying an image’s digest when it matters that you run exactly the version you tested, and keeping images patched and rebuilt regularly are all run-time-adjacent habits that meaningfully reduce risk, even though they happen slightly before the run command itself.
12Monitoring, Logging and Metrics
Once a container is running unattended, you need a way to know it is actually healthy, not just technically still alive.
Every container’s standard output and standard error, the two text streams a program normally prints, are captured automatically by the engine’s logging driver. The simplest way to inspect them is a command that reads a specific container’s captured output directly, optionally following it live as new lines arrive, which is often the very first thing to check when something seems wrong with a running container.
For a quick live snapshot of resource usage, a separate command shows processor use, memory use, network traffic and disk activity for one or more running containers at once, drawn straight from the same cgroup counters that were set up when the container was started. This is ideal for a fast manual check; production systems typically export the same numbers continuously to a dedicated metrics platform so trends can be graphed and alerts raised automatically.
| Command purpose | What it shows you |
|---|---|
| View logs | What the application inside the container has printed, in order, including recent errors |
| Live stats | Current CPU, memory, network and disk usage for running containers |
| Inspect | Full configuration and current state of a specific container, including its IP address and mounted volumes |
| Health status | Whether a HEALTHCHECK instruction defined in the image currently reports the container as healthy |
A container can sit in the running state indefinitely while the application inside it is frozen, deadlocked, or stuck retrying a failed connection. If the image defines a HEALTHCHECK instruction, the engine periodically runs a small test inside the container, such as requesting a page from a web server, and marks it unhealthy if that test keeps failing, giving automated systems a signal to restart or replace it long before a human would otherwise notice.
As more containers are run side by side, a single user request often passes through several of them before a response is finally sent back, one container handling authentication, another handling the actual business logic, another writing to a database. Distributed tracing attaches one shared identifier to a request as it travels across this chain of containers, so that when something along the way is unusually slow or fails outright, an engineer can see the exact path that single request took, and exactly how long each container spent on it, instead of manually comparing timestamps across several separate log files and guessing at which entries actually belong together.
13Design Patterns and Anti-patterns
Certain ways of running containers work reliably again and again. Others cause the same headaches under a different name every time.
One responsibility per container
Running the web server, the database, and a background worker as three separate containers, each with its own restart policy and resource limits, rather than bundling them together.
Explicit restart policy always set
Deciding on purpose whether a container should restart automatically, instead of silently accepting the engine’s default of never restarting it.
Sidecar container
Running a small helper container, such as a log shipper, alongside the main application container, sharing its network or volumes, without changing the main application at all.
Configuration through environment variables
Passing settings that differ between environments, like a database address, as environment variables at run time rather than baking separate images for each environment.
What it looks like
Running a stateful service such as a database with no volume mounted at all, because everything appears to work correctly during development and initial testing.
Why it hurts later
The very first time that container needs to be replaced, whether to apply a security update or simply to move it to a new machine, every piece of data it held disappears permanently and without any warning at the moment it happens.
The fix
Mount a named volume for any location where the application keeps data it cannot afford to lose, the very first time the container is ever run, not after the first painful lesson.
What it looks like
Starting a long-lived service in foreground mode inside a terminal session, perhaps inside a remote connection, and simply leaving that terminal open indefinitely to keep the service alive.
Why it hurts later
Any interruption to that terminal session, a dropped network connection, a closed laptop lid, an accidental key press, stops the service immediately, often at the worst possible moment, with no automatic recovery.
The fix
Run long-lived services in detached mode with an appropriate restart policy, so their availability no longer depends on a single terminal window staying open.
14Best Practices and Common Mistakes
A short, practical checklist that resolves most of the problems people run into the first time they run containers for real.
Always name your containers
An explicit, readable name turns every future command into something you can type confidently instead of copying a random ID from a list.
Decide the restart policy on purpose
Choose “unless-stopped” or “always” for services meant to stay up, rather than silently accepting the engine’s default of no automatic restart.
Mount a volume for anything stateful
If losing the data would hurt, it belongs in a named volume from the very first run, not added after the first data-loss incident.
Set memory and CPU limits
Even a generous limit is better than no limit, since it protects every other container sharing that same host machine.
Keep secrets out of the command history
Passwords and API keys typed directly into a run command often end up saved in a shell’s history file; prefer a secrets manager or a file read at start-up instead.
Check logs and health before declaring success
A container that started without an error message has not necessarily started correctly; check its logs and, if defined, its health status before moving on.
Typing docker run again to bring back a container you already created earlier creates an entirely new, separate container instead of restarting the existing one, silently leaving the old one behind. Use a restart command on the existing container’s name when you simply want it running again.
Cleaning up after yourself
Every container that is run and later stopped, but never removed, continues to sit on disk holding its own writable layer, its logs, and its configuration, contributing to a slow, easy-to-miss build-up of unused disk space on any machine used for regular development or testing. A periodic cleanup command lets you remove every stopped container, unused network, and dangling image in one step, freeing that space back up. On a shared development or staging machine, agreeing on a simple habit, such as running this cleanup at the end of each day or automatically on a schedule, prevents the slow, quiet accumulation of forgotten containers from ever becoming a real problem that someone has to investigate later.
15Real-World and Industry Examples
The choices covered in this guide are made, at enormous scale, by some of the best-known technology companies in the world, every single day.
Netflix
Runs a large share of its backend services as containers, each launched with its own resource limits and restart behaviour, so that a failure in one small service does not cascade into the rest of the system during peak viewing hours.
Amazon
Amazon ECS and Amazon EKS ultimately translate a customer’s desired container configuration, restart policy, resource limits, networking and all, into the same kind of run-time decisions covered in this guide, just applied automatically across an entire fleet of machines.
Uber
Runs a very large number of independently deployable services in containers, each configured with its own resource limits and health checks, so that one team’s service under heavy load cannot silently starve another team’s unrelated service.
Pioneered many of the ideas behind automatically restarting failed containers and enforcing resource limits at massive scale, work that directly shaped both Docker’s own defaults and the design of Kubernetes.
Payments: Stripe
Runs many of its internal services as containers with strict, individually tuned resource limits and health checks, reflecting how seriously a payments company must treat the reliability of every single running process, since even a brief, unnoticed failure in one small service can affect real financial transactions.
Whether it is one developer running a single container on a laptop or a global company running millions of them, the same basic run-time decisions covered in this guide, naming, networking, persistence, restart behaviour, resource limits, and security, are exactly what determine whether a container behaves reliably or becomes a source of quiet, hard-to-diagnose problems later.
16Frequently Asked Questions
17Summary and Key Takeaways
Carry these ideas forward
- Running a container turns a frozen image into a live process by asking the daemon, then containerd, then runc, to build an isolated space and start the image’s main command inside it.
- Isolation at run time comes from ordinary Linux namespaces and control groups, not from a separate pretend computer, which is exactly why a container starts in a fraction of a second.
- A container moves through five real states, created, running, paused, stopped and removed, and stopping is a polite request with a grace period, not an instant kill.
- A small set of run-time options, naming, detached mode, published ports, mounted volumes, environment variables and a restart policy, cover the overwhelming majority of real situations.
- Anything written inside a container without a mounted volume disappears the moment that container is removed, so persistence must be planned before the first run, not after data is lost.
- Security at run time is about privilege: a non-root user, dropped capabilities, and avoiding the privileged flag keep a compromised application boxed in rather than handed a shortcut past every wall.
- A container that is “running” is not automatically “healthy”; logs, live stats, and a defined health check are what actually confirm it is doing its job correctly.