The fastest answer to “how do I run a docker image” is docker run <image>. That works on a developer’s laptop for a quick test. The honest version of running a Docker image — locally, in CI, or in production — covers the five flags that matter (port mapping, environment, volume, name, restart), the four pitfalls that break the first time you try it (image not found, port already in use, env vars ignored, network unreachable), and the deploy path that gets you from a working local container to a service that survives a machine restart.
Table of contents
- Table of contents
- The direct answer
- The five flags that matter for any real run
- The four pitfalls that break the first time
- The volume pattern for persistent data
- The network pattern for multi-container apps
- The restart policy and the deploy path
- The healthcheck pattern for monitoring
- FAQ
- FAQ
The short version for a container that survives a real environment: docker run -d --name <name> --restart unless-stopped -p <host>:<container> -e <KEY>=<VALUE> -v <host>:<container> <image>. That is the minimum. Add --network for multi-container setups, --health-cmd for monitoring, and --read-only for hardening.
Table of contents
- The direct answer
- The five flags that matter for any real run
- The four pitfalls that break the first time
- The volume pattern for persistent data
- The network pattern for multi-container apps
- The restart policy and the deploy path
- The healthcheck pattern for monitoring
- FAQ
The direct answer
The minimum useful run:
docker run -d --name myapp -p 8080:8080 -e DATABASE_URL=postgresql://localhost/mydb --restart unless-stopped myimage:latest
That runs the container detached, names it, maps port 8080, passes an env var, and configures restart policy. The container survives a machine reboot and exposes itself on the host’s port 8080.
The version for production adds a volume for persistent data, a healthcheck for monitoring, and a network for talking to other containers. See below.
The five flags that matter for any real run
-d (detached). Run the container in the background. Without this, the container’s stdout streams to your terminal. With it, the container runs as a daemon and docker run returns immediately.
--name <name>. Give the container a stable name. Without a name, Docker assigns a random one (vigilant_bell). With a name, you can docker stop myapp, docker logs myapp, and reference the container from other containers via Docker networking.
-p <host>:<container> (publish). Map a port on the host to a port on the container. -p 8080:8080 makes the container’s port 8080 reachable on the host’s port 8080. Without -p, the container’s ports are unreachable from outside (unless you use --network host, which is a different pattern with different trade-offs).
-e <KEY>=<VALUE> (env). Pass environment variables to the container. Use -e for one-offs or --env-file for many. The container sees the env vars as if they were set in the shell.
--restart <policy>. What to do when the container exits. The options:
no— never restart (default).on-failure— restart only on non-zero exit, max N times.always— restart always, including ondocker restart.unless-stopped— restart always, except when the user explicitly stops the container.
For most production containers, unless-stopped is the right answer. It survives machine reboots but respects manual stops.
The bonus flags:
-v <host>:<container>or--mount type=volume,...— mount persistent storage.--network <name>— connect to a specific Docker network.--health-cmd <cmd>— define a healthcheck for monitoring.--read-only— make the root filesystem read-only for hardening.--memory <limit>and--cpus <limit>— set resource limits.
The four pitfalls that break the first time
-
Image not found.
docker run myimagefails withUnable to find image 'myimage:latest' locally. Fix: eitherdocker pull myimagefirst, or use the full image reference (docker.io/library/myimage:latest,ghcr.io/myorg/myimage:v1.2.3, etc.). -
Port already in use.
-p 8080:8080fails withbind: address already in use. Fix: either stop the other process using 8080 (lsof -i :8080to find it), or pick a different host port (-p 8081:8080). -
Env vars ignored. The container starts but the app says “DATABASE_URL not set.” Fix: the env var is being passed to a different process than the one running. Check the Dockerfile’s
ENTRYPOINTandCMD— the env vars are visible to the shell that runs them. For complex multi-process containers, usedocker exec myapp envto see the actual env vars inside the container. -
Network unreachable. The container starts, the app inside tries to reach
localhost:5432, fails. Fix:localhostinside a container is the container itself, not the host. To reach the host’s services, usehost.docker.internal(Docker Desktop) or the host’s IP (Linux). For other containers on the same Docker network, use the container’s--nameas the hostname.
A fifth pitfall that shows up at deploy time: the container runs fine until a machine restart, then does not come back. Fix: --restart unless-stopped (see below).
The volume pattern for persistent data
Containers are ephemeral. The filesystem inside a container is gone when the container is deleted. For data that survives container restarts, mount a volume:
docker run -d --name postgres -v pgdata:/var/lib/postgresql/data postgres:16
The -v pgdata:/var/lib/postgresql/data creates a named volume pgdata and mounts it at the container’s data directory. When the container is deleted and recreated, the data persists.
For data that lives outside Docker (development databases, log files you want to inspect), use a bind mount:
docker run -d --name postgres -v /var/lib/pgdata:/var/lib/postgresql/data postgres:16
The bind mount maps a host directory into the container. The host directory must exist or Docker creates it (as root).
The pattern that survives a real environment:
- Named volume for production data. Docker manages the location; the volume survives container deletion.
- Bind mount for development. You can inspect the data on the host filesystem.
- Tmpfs mount for secrets.
-v /tmp/secrets:/run/secrets:rofor credentials that should not be persisted.
The network pattern for multi-container apps
A single container is unusual. Most apps have at least a backend and a database. The pattern is a user-defined Docker network:
# Create the network
docker network create myapp
# Run the database
docker run -d --name db --network myapp -e POSTGRES_PASSWORD=secret postgres:16
# Run the backend, talking to the database via its name
docker run -d --name api --network myapp -e DATABASE_URL=postgresql://postgres:secret@db:5432/mydb -p 8080:8080 myapp:latest
The --network myapp flag puts both containers on the same network. The backend references the database as db (the database container’s name). Docker’s embedded DNS resolves db to the database’s IP.
The pattern that does not work: hardcoding IP addresses. Container IPs are not stable across restarts. Use names.
The pattern that is fragile: using host.docker.internal. That works on Docker Desktop (macOS, Windows) but not on Linux. For cross-platform consistency, use a user-defined network with named containers.
The restart policy and the deploy path
The restart policy is what makes a container survive a machine reboot. Without it, the container starts when you run docker run and stays running until the container exits or the machine shuts down. After a reboot, the container does not come back automatically.
The four policies:
--restart no # Never restart (default)
--restart on-failure # Restart on non-zero exit, max 5 times
--restart always # Always restart
--restart unless-stopped # Restart always, except when stopped manually
For most production containers, unless-stopped is right. It survives machine reboots, respects docker stop, and does not aggressively restart on bugs that would otherwise cause a crash loop.
The deploy path that works:
- Build the new image:
docker build -t myapp:v1.2.3 . - Stop the old container:
docker stop api - Remove the old container:
docker rm api - Start the new container:
docker run -d --name api --network myapp ... myapp:v1.2.3
The window between step 2 and step 4 is downtime. For zero-downtime deploys, use docker-compose, Kubernetes, or a managed platform with rolling deploys.
The healthcheck pattern for monitoring
A container can be running but not actually serving traffic. The healthcheck tells Docker (and external monitoring) whether the container is healthy:
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 CMD curl -f http://localhost:8080/health || exit 1
The --interval is how often to run the check. --timeout is how long to wait for the check to respond. --start-period is a grace period after start. --retries is how many failures before marking unhealthy.
Once the healthcheck is defined, docker ps shows the status: Up 5 minutes (healthy) or Up 5 minutes (unhealthy).
For external monitoring (Prometheus, Datadog, uptime checkers), the healthcheck endpoint is usually a /health or /healthz route on the app. The endpoint should:
- Return 200 if the app is ready to serve traffic.
- Return 503 if the app is starting up or has a dependency failure.
- Be fast (< 100 ms) and cheap (no DB query, no auth check).
If you are running a container in production on a managed platform like RunxBuild’s backend services, the platform runs the container with a sensible restart policy, the healthcheck is one line in the Dockerfile, and the platform’s load balancer routes traffic only to healthy instances. For what running that container at production scale costs, the RunxBuild hosting calculator gives you the per-month number.
FAQ
What is the difference between docker run and docker start?
docker run creates a new container from an image and starts it. docker start starts an existing container (created previously with docker create or stopped with docker stop). Use docker run for new containers; docker start for re-runs.
How do I see the logs of a running container?
docker logs <name> or docker logs -f <name> to follow (tail -f style). The logs include stdout and stderr from the container’s main process.
How do I run a command inside a running container?
docker exec -it <name> <command>. For an interactive shell: docker exec -it <name> bash. For a one-off: docker exec <name> ls /app.
Can I run multiple processes inside one container?
Technically yes, but it is an anti-pattern. One process per container is the convention. Use a process manager (supervisord, s6-overlay) only when you genuinely need multiple processes (e.g. an app and a sidecar).
What is the difference between -p and --network host?
-p maps a host port to a container port (default bridge network). --network host removes the network namespace and uses the host’s network directly. The latter is faster but loses container isolation.
How do I limit a container’s memory?
docker run --memory=512m .... The container is killed if it tries to use more. Use --memory-swap to control swap behavior.
How do I update a running container without downtime?
docker run does not update — it creates a new container. For in-place updates, use docker-compose up -d --no-deps <service> or a managed platform with rolling deploys.
What is the difference between an image and a container?
An image is the read-only template (filesystem + metadata). A container is a running instance of an image (image + writable layer + runtime state). One image can spawn many containers.
FAQ
What is the difference between docker run and docker start?
docker run creates a new container from an image and starts it. docker start starts an existing stopped container.
How do I see the logs of a running container?
docker logs <name> or docker logs -f <name> to follow (tail -f style). The logs include stdout and stderr.
How do I run a command inside a running container?
docker exec -it <name> <command>. For a shell: docker exec -it <name> bash.
Can I run multiple processes inside one container?
Technically yes, but it is an anti-pattern. One process per container is the convention.
What is the difference between -p and --network host?
-p maps a host port to a container port. --network host removes the network namespace and uses the host’s network directly.
How do I limit a container’s memory?
docker run --memory=512m .... The container is killed if it tries to use more.
How do I update a running container without downtime?
docker run does not update. Use docker-compose up -d --no-deps <service> or a managed platform with rolling deploys.
What is the difference between an image and a container?
An image is the read-only template. A container is a running instance of an image with a writable layer and runtime state.