Running a Container in Docker

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.

Everyday analogy

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.

1
command usually starts a container: docker run
<1 sec
typical start-up time once the image is local
5
lifecycle states a container can pass through

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.

i
“Run” versus “start”

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.

Command

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.

Naming

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.

Mode

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.

Entry point

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.

Connectivity

Published port

A rule that forwards traffic arriving at a port on the host machine into a specific port inside the running container.

Persistence

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.

Configuration

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.

Reliability

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.

✓
Good habit

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"]
Figure 1: The path from a typed command to a live 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.

Everyday analogy

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"]
Figure 2: What happens in the moment before your code starts

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 common myth

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
Figure 3: The round trip of a single detached docker run command

The five states, one by one

1

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.

2

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.

3

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.

4

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.

5

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.

✓
Stop versus kill versus remove

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.

Background

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.

Identity

Container name

Gives the container a fixed, human-readable name instead of a randomly generated one, so later commands are simple and unambiguous.

Network

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.

Storage

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.

Config

Environment variable

Passes a piece of runtime configuration, such as a database password or a feature flag, into the container without editing the image.

Reliability

Restart policy

Tells the engine to automatically restart the container if it stops unexpectedly, or when the whole host machine reboots.

Cleanup

Auto-remove

Automatically deletes the container the instant it stops, handy for short-lived, one-off tasks that should leave nothing behind.

Limits

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

PolicyBehaviourTypical use
noNever restart automatically. This is the default if nothing is specified.One-off jobs and quick manual tests
on-failureRestart 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
alwaysAlways restart, no matter how the container stopped, including after the host reboots.Long-running services such as web servers or databases
unless-stoppedBehaves like always, except it will not restart automatically if a person deliberately stopped it.Most production services; respects a manual stop
!
A frequent beginner mistake

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
Figure 4: How a published port and internal DNS route traffic
Everyday analogy

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.

Recommended

Named volume

Fully managed by Docker itself. The safest and simplest default for a database’s files or any data you must never lose.

Development

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.

Speed, no persistence

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.

!
The trap that catches almost every beginner once

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.

SituationRecommended mode
Quickly testing a command or exploring an imageForeground, so you see the output immediately
A web server, database, or any long-running serviceDetached, combined with a restart policy
A one-off script that should clean up after itselfForeground with the auto-remove option
✓
Looking inside a running container

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
“The run command is simple to type and easy to get subtly wrong. Most production incidents involving containers trace back to a missing option, not a broken tool.”

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.

Identity

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

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.

Capabilities

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.

Danger flag

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.

PATTERN · LEAST PRIVILEGE AT RUN TIMERECOMMENDED
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.

!
A specific risk worth naming

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 purposeWhat it shows you
View logsWhat the application inside the container has printed, in order, including recent errors
Live statsCurrent CPU, memory, network and disk usage for running containers
InspectFull configuration and current state of a specific container, including its IP address and mounted volumes
Health statusWhether a HEALTHCHECK instruction defined in the image currently reports the container as healthy
✓
Running is not the same 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.

Pattern

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.

Pattern

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.

Pattern

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.

Pattern

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.

ANTI-PATTERN · SILENT DATA LOSSAVOID
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.

ANTI-PATTERN · THE FOREVER-FOREGROUND HABITAVOID
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.

!
Common mistake: reaching for run instead of start

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.

Streaming

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.

Cloud provider

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.

Ride-hailing

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.

Search and cloud

Google

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

Q1What happens if I run the same image twice without stopping the first container?
You get two completely separate, independent containers running from the same image at the same time, each with its own writable layer, its own ID, and, if a fixed name was reused, an error, since two containers cannot share the same name simultaneously.
Q2Does stopping a container delete it?
No. A stopped container still exists, still holds its writable layer and configuration, and can be started again later with the restart command. Only an explicit remove command, or the auto-remove option set at run time, actually deletes it.
Q3Why does my container exit immediately after I run it?
A container’s life is tied entirely to its main process. If that process finishes quickly, or crashes on start-up, for example because a required environment variable was missing, the container stops right away, even though it briefly and genuinely ran. Checking its logs immediately after almost always reveals the reason.
Q4Can I change the options a container was run with, like its published port, after the fact?
Not on the same container. Run-time options such as published ports and mounted volumes are fixed when a container is created. To change them, you remove the old container and run a new one from the same image with the updated options; this is why storing anything important in a volume, kept separate from the container itself, matters so much.
Q5Is it safe to run several containers that publish the same host port?
No, only one process on the host can bind a given port at a time, so the second container will fail to start with a port-already-in-use error. Each container needing external access on the same host must publish to a different host port.
Q6What is the difference between detaching from a container and stopping it?
Detaching, using the specific key combination meant for that purpose, leaves the container running in the background and simply disconnects your terminal from watching its output. Stopping actually ends the container’s main process. Accidentally using the wrong key combination to exit an attached session can stop a container you only meant to step away from.
Q7Do I need to rebuild the image every time I change a run-time option like a port or a volume?
No. Options such as published ports, mounted volumes, environment variables and restart policy are all applied when a container is created from an image, not baked into the image itself. Changing any of them only requires removing the existing container and running a new one from the same, unchanged image with the updated options.
Q8Why does my running container disappear as soon as it finishes its task?
This is expected if the container was started with the auto-remove option, which deletes a container automatically the instant its main process exits, useful for short-lived jobs that should leave nothing behind. If auto-remove was not requested, a finished container instead moves into the stopped state and remains inspectable until it is explicitly removed.

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.