← Back to the section

You have an image — now you need to run it. In this article we will walk through the docker run command, the flags you use every day, and what happens to a container after it starts: how to view logs, step inside, and stop it cleanly.

What happens on start

When you type docker run, Docker creates a container from the image — an isolated process with its own file system, network, and environment variables — and starts it.

Short formula: an image is the recipe, a container is the cooked dish.

The simplest run:

docker run eclipse-temurin:21-jre java -version

Docker pulls the image (if it is not present locally), creates a container, runs the command, and exits. The container stops as soon as the process inside it finishes.

Core docker run flags

Most real-world runs use several flags together. Let's go through each one.

-p: port mapping

A container lives in an isolated network — you cannot reach it from the outside until you explicitly open a port.

docker run -p 8080:8080 my-spring-app
#              ^     ^
#         host:container

The left side is the port on your machine (the host), the right side is the port inside the container. You can use different numbers:

docker run -p 9090:8080 my-spring-app   # the app listens on 8080, reachable from outside on 9090

-e: environment variables

This is the main way to pass configuration into a container — database passwords, URLs of external services, Spring Boot profiles. Each variable is a separate -e flag:

docker run \
  -e SPRING_PROFILES_ACTIVE=prod \
  -e DB_URL=jdbc:postgresql://db:5432/myapp \
  -e DB_PASSWORD=secret \
  my-spring-app

Inside Spring Boot these variables are automatically picked up as application properties (through Environment).

-d: detached mode

By default docker run keeps the terminal busy — the output goes straight to the console. The -d (detached) flag runs the container in the background and returns control immediately:

docker run -d -p 8080:8080 my-spring-app

Docker prints the container id and that's it. You view logs separately — more on that below.

--name: container name

By default Docker makes up names like elegant_hopper — not very convenient. Set your own:

docker run -d --name my-app -p 8080:8080 my-spring-app

Now you can refer to the container by name in every command: docker logs my-app, docker stop my-app.

--rm: auto-remove

If you need the container only once (to run a test, execute a script), the --rm flag removes it automatically when it stops:

docker run --rm my-spring-app java -jar app.jar --check-config

Without --rm, stopped containers pile up and take up space.

Everything together — a typical run

docker run -d \
  --name my-app \
  --rm \
  -p 8080:8080 \
  -e SPRING_PROFILES_ACTIVE=prod \
  -e DB_URL=jdbc:postgresql://db:5432/myapp \
  my-spring-app:1.0

Passing configuration through environment variables

In the container world, configuration is not stored inside the image — it comes from outside at run time. This is an idea from the 12-factor app methodology: the same image is deployed across different environments (development, testing, production) with different variables.

For Spring Boot it looks like this: in application.yml you write ${DB_URL}, and you pass the actual value through -e DB_URL=... when you start the container. No secret ever ends up in the image.

If there are many variables, it is convenient to move them into a file:

# .env
SPRING_PROFILES_ACTIVE=prod
DB_URL=jdbc:postgresql://db:5432/myapp
DB_PASSWORD=secret
docker run -d --env-file .env -p 8080:8080 my-spring-app

Just don't commit a .env with real passwords into the repository.

The container lifecycle

After it starts, a container moves through several states.

diagram

Commands for managing it:

docker ps                  # list of running containers
docker ps -a               # all of them, including stopped
docker stop my-app         # graceful stop (SIGTERM, then SIGKILL after 10s)
docker start my-app        # start a stopped container
docker rm my-app           # remove a stopped container
docker rm -f my-app        # stop and remove at once

docker stop sends the process a SIGTERM signal — the application can catch it and shut down cleanly (close database connections, wait for current requests). After 10 seconds Docker forcibly kills the process with SIGKILL.

Logs: docker logs

Everything the application writes to stdout and stderr is collected by Docker automatically.

docker logs my-app            # print all accumulated logs
docker logs -f my-app         # follow the logs in real time (like tail -f)
docker logs --tail 100 my-app # the last 100 lines

Spring Boot writes to stdout by default — you don't need to configure anything extra. If the application writes to a file inside the container, docker logs will not see that file.

Step inside: docker exec

Sometimes you need to see what is happening inside a running container — check the file system, environment variables, network connections.

docker exec -it my-app sh     # start a shell inside the container
docker exec -it my-app bash   # if bash is present in the image

The -it flags mean: -i — forward keyboard input, -t — attach a pseudo-terminal. Together they give you an interactive session.

Inside you can check the variables:

env | grep SPRING

Or look at which processes are running:

ps aux

docker exec does not restart the application — the command runs alongside the already-running process.

Healthcheck: verifying it works

Docker can periodically check whether a container is alive — not just that the process is running, but that the application actually responds. This is a healthcheck.

The easiest way is to define it in the Dockerfile:

HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
  CMD wget -qO- http://localhost:8080/actuator/health || exit 1

After it starts, the container spends some time in the starting state — Docker waits for the healthcheck to pass for the first time. Then it moves to healthy or unhealthy.

To view the status:

docker inspect --format='{{.State.Health.Status}}' my-app

In an isolated docker run, a healthcheck only affects the status — the container is not restarted automatically. Automatic restart is already a job for Docker Compose or an orchestrator.

In short

  • docker run creates a container from an image and starts it; when the process finishes, the container stops.
  • -p host:container — port mapping; without it you cannot connect to the container from the outside.
  • -e KEY=VALUE — pass an environment variable; this is the main way to configure things following the 12-factor principle.
  • -d — detached mode; --name — a convenient name; --rm — remove the container after it stops.
  • docker logs -f — follow the logs in real time; docker exec -it ... sh — an interactive session inside.
  • docker stop sends SIGTERM, giving the application 10 seconds to shut down cleanly.
  • A healthcheck verifies that the application is not just running but actually responding.
  • What Docker is and why you need it — if you haven't read it yet: images, layers, isolation from scratch.
  • Volumes and data — how to keep data across container restarts.
  • Networking in Docker — how containers talk to each other and to the outside world.