Skip to content
Blog

Foreground vs Detached Mode: When to Run Docker Containers in the Background

Foreground or -d? How attached output, Ctrl-C, Ctrl-P Ctrl-Q detach, docker logs and restart policies interact — and how to pick the right mode every time.

Published on • October 31, 2026

AI Assistant

Every docker run has exactly two personalities. In foreground mode (the default) the CLI stays attached to the container’s output streams and blocks your terminal until the container exits. With -d (--detach) the CLI returns immediately after printing a container ID, and the container quietly does its work in the background. The flag is one character; getting it wrong costs you either a blocked terminal or hours spent hunting for logs that were there all along.

What foreground mode gives you

Run a container with no -d and the Docker CLI binds itself to the container’s STDOUT and STDERR:

$ docker run --name worker my-app:latest
2026-10-31T08:12:01Z starting up
2026-10-31T08:12:01Z listening on :8080
^C

What you get:

  • Live output with no extra commands. What the process prints is what you see — ideal for watching a migration, a build, or a crash loop as it happens.
  • The exit code. When the container stops, docker run returns the container’s exit status to your shell, so docker run ... && echo ok works as you’d expect in scripts.
  • Ctrl-C works. Pressing Ctrl-C sends SIGINT to the container’s main process (the CLI proxies received signals to the container by default — --sig-proxy defaults to true), and the container stops with it.
  • Graceful detaching. Press Ctrl-P then Ctrl-Q and the client disconnects without stopping the container. The container keeps running; you get your shell back. This is the standard escape hatch, and you can change the key sequence with --detach-keys if it collides with your terminal.

A detail worth internalizing: attaching is not the same as logging. Output is captured by the configured logging driver whether or not anyone is attached. You can detach, close the terminal, go home — and the lines are still waiting in docker logs.

Foreground also composes with -i (keep STDIN open) and -t (allocate a pseudo-TTY), which is why interactive shells are almost always written -it:

$ docker run -it --name shell alpine sh
/ # ls

Without -t, some programs behave oddly (prompts without echo suppression, no job control); with -t but no -i, there is no input at all. Neither flag changes where output goes — only how it reaches your terminal.

What -d does

$ docker run -d --name web -p 80:80 nginx:alpine
4bed76d3ad428b889c56c1ecc2bf2ed95cb08256db22dc5ef5863e1d03252a19

-d prints the container ID and returns. Your terminal is free, and the container is now a background service of the Docker daemon. Two consequences from the docker run reference:

  • A detached container exits when its main process exits. There is no terminal holding it alive. If your command daemonizes itself (service nginx start, daemonize, nohup ... &) and returns, the container stops immediately — often leaving a half-started service that appears to “flash” in docker ps. Run the server in the foreground inside the container instead: nginx -g 'daemon off;', docker run -d my-image ./app-server.
  • I/O happens elsewhere. The container is no longer listening to the command line where docker run was run. To interact with it, use ports, shared volumes, docker exec, or docker attach.

When each mode is right

ForegroundDetached (-d)
Typical useDebugging, one-off jobs, dev iterationServices, workers, anything long-running
OutputOn your terminal instantlyOnly via docker logs / docker attach
TerminalOccupied until exitFree immediately
Exit codeReturned to your shellHidden in docker inspect
Ctrl-CStops the containerKills your client only (container keeps running)
Pairs with --restartPoorlyNaturally

Use foreground when the command is the task: a migration, a test suite, a quick busybox probe, or the first five minutes of debugging a container that misbehaves. If it will finish, you want its exit code and its output in the same place.

Use -d for anything meant to outlive your shell session: web servers, databases, queue consumers, agents. Add --name, and pair it with a restart policy (--restart=unless-stopped) so the service survives daemon restarts.

A hybrid that covers most workflows: start detached, then follow the output:

$ docker run -d --name api my-api:latest
$ docker logs -f api

The restart-policy trap

Attaching and restart policies don’t mix well. As the docker run reference puts it: on container restart, attached clients are disconnected. A foreground docker run --restart=always ... returns to your shell when the process exits while the daemon restarts the container in the background — you are no longer watching anything, and you may not notice. (--restart combined with --rm is rejected outright as contradictory.)

Rule of thumb: restart policies belong to -d; foreground belongs to --rm or to jobs that should run exactly once.

The --attach flags: pick your streams

-d is not all-or-nothing. --attach (-a) selects which streams a docker run binds to, and you can combine it with -d to filter what lands in your terminal or script:

# print only errors to the console; normal output stays in the logs
$ docker run -a stderr ubuntu echo test
4bed76d3ad428b889c56c1ecc2bf2ed95cb08256db22dc5ef5863e1d03252a19

# pipe stdin into a background job and grab its ID
$ cat somefile | docker run -d -i -a stdin mybuilder dobuild

To rejoin a running detached container later:

$ docker attach web        # Ctrl-P Ctrl-Q to leave again without stopping it
$ docker detach web       # detach programmatically

Common mistakes

“I ran -d and now there are no logs.” There are logs — docker logs reads whatever the container’s process wrote to STDOUT/STDERR, regardless of mode. If docker logs shows nothing, the real problem is usually that the application writes to files inside the container instead of the standard streams (the official nginx image solves this by symlinking its log files to /dev/stdout and /dev/stderr). Check with docker inspect -f '{{.HostConfig.LogConfig.Type}}' <container> which driver is storing them.

“My terminal froze / I can’t Ctrl-C it.” A foreground container whose main process ignores signals will not react to Ctrl-C — particularly PID 1, which ignores signals whose default action would terminate it unless it installs handlers. Escape with Ctrl-P Ctrl-Q, then stop it properly with docker stop <name>.

“I closed the terminal and the container died.” Only if it was running in foreground and something sent it a signal. Closing an attached terminal can deliver SIGHUP; run services with -d so they don’t depend on your session.

“I Ctrl-C’d in the wrong terminal.” Ctrl-C only reaches containers attached to that terminal. From any other terminal, stop a stray foreground container with docker stop <name> or docker kill <name> — there is nothing to interrupt client-side.

“I docker exec’d in and killed my app.” docker exec processes are not PID 1 and share the container’s fate only indirectly; killing the exec’d shell does nothing to the main process. Stop the container itself.

Further reading