Table of Contents
Why Multi Container Applications Matter
Up to this point you have mostly seen single containers in isolation. In real projects, applications are rarely just one thing. A typical web app often needs a web server, an application server, a database, maybe a cache, and some background workers. Docker Compose exists to coordinate these multiple containers so they behave like one application from a developer’s point of view.
Instead of manually starting several containers with separate docker run commands, trying to remember the right environment variables, port mappings, and network settings, you declare everything once in a docker-compose.yml file. Then you use a single command to start the whole group of containers together.
This chapter focuses on how to actually run, control, and observe multi container applications with Compose, not on the full structure of the YAML file or how to design individual services, which are covered in other chapters.
Starting a Multi Container Application
When you have a docker-compose.yml file in a directory, that directory becomes the home of your application stack. From inside that directory, the simplest way to start all defined services is:
docker compose upCompose reads the file, creates a dedicated network, creates any declared volumes, builds images if needed, and finally starts all services as containers. The containers share a project name, which by default is the directory name. This project name is used as a prefix for container names, network names, and volume names.
If your configuration uses images that must be downloaded, Compose automatically pulls them from the configured registries before starting the containers. If your configuration references local Dockerfiles, Compose builds those images according to the build settings.
Important rule: docker compose up without extra options starts all services defined in the current directory's docker-compose.yml, using the directory name as the project name.
Foreground vs Background Execution
By default, docker compose up runs in the foreground and attaches to the logs of all started containers. You see interleaved log lines from each service, usually prefixed with the service name. This is very useful during development, because you can watch how the services start up and how they talk to each other.
However, keeping a terminal permanently occupied might not be ideal. To run the same application in the background you can use detached mode:
docker compose up -dIn detached mode, Compose starts everything and then returns you to the shell prompt. The containers keep running in the background on your machine.
You can switch between these modes at any time by stopping and starting the application again. The containers themselves are the same, only the way you interact with them through logs changes.
Building and Rebuilding Services
A multi container setup often includes services that are built from local source code. Compose can build these images when you start the application. If a service has a build section in the configuration, docker compose up triggers a build step before creating or updating that service.
During development, you frequently change the application code. As long as those changes are mounted into the container with volumes, you may not need a rebuild. If changes affect dependencies, base layers, or build steps, you need to rebuild the images for the relevant services.
You can combine building and starting like this:
docker compose up --buildThis forces a rebuild of images defined with build instructions before starting. If you only want to rebuild without launching containers, or if you want to refresh images while services are already running, you can run:
docker compose buildYou can also target a single service:
docker compose build webThis approach limits changes to specific services rather than rebuilding the entire stack every time.
Viewing and Following Logs
When several containers run together, log output becomes more important for understanding what is happening. Compose provides a combined view of logs across all services or a filtered view per service.
If you started Compose in attached mode with docker compose up you already see live logs. If you used detached mode, you can attach to the logs at any time with:
docker compose logs
This prints the full log history since the containers started. To keep following new log entries in real time, similar to tail -f, you can use:
docker compose logs -fYou can filter by service name when you only care about one part of the stack:
docker compose logs -f webThis way you can monitor the web service while the database and other services continue to run quietly in the background.
Compose handles the multiplexing of logs from multiple containers and adds prefixes that show which service each line belongs to. This makes reading multi container logs much easier than inspecting containers one by one with low level Docker commands.
Inspecting the State of the Application
To understand what is running as part of your multi container application, you can list all services and their current state:
docker compose psThis displays status, container names, ports, and other useful fields for each service. The output is scoped to the Compose project in the current directory, so you only see containers that belong to this application.
If you want more detailed information about a specific service or about the internal configuration Compose uses, you can use:
docker compose config
This shows the final configuration after resolving environment variables and combining multiple Compose files. While this is more about configuration than runtime state, it helps you verify what services and settings Compose actually uses when you call up.
You can also use standard Docker commands such as docker ps to see all containers on your system, including those managed by Compose, but docker compose ps keeps your focus on one application at a time.
Stopping and Restarting Multi Container Applications
When you no longer need the application to run, you can stop it as a whole. For a Compose stack, this is done with:
docker compose stopThis sends a stop signal to each container in the project and waits for them to exit gracefully. The containers themselves remain available on your system in a stopped state, along with their network and volumes. You can restart them later without recreating them:
docker compose startIf you want a complete cycle that stops and then starts again with a single command, for example after changing configuration or environment variables, you can run:
docker compose restart
This restarts all services, or you can target a single one, such as docker compose restart web. Restarting is helpful when you adjust non hot reloadable things like environment variables, port bindings, or command parameters for just one service.
Removing Containers and Cleaning Up
Sometimes you want to remove containers that belong to your application, for instance to free resources or to ensure a clean state. Compose includes a specific command for this:
docker compose down
This stops and removes the containers, also removing the default network created for the project. By default, docker compose down does not remove named volumes that are declared in the configuration, so persistent data such as your database contents remains intact.
If you want to also remove volumes that are attached to your services, you can add a flag:
docker compose down -v
Important rule: docker compose down removes the containers and the project network. Use docker compose down -v if you also want associated volumes and their data to be deleted.
This distinction is crucial because in multi container setups volumes often carry valuable data that must not disappear on every shutdown. Only use the volume removal option when you are certain that you no longer need the stored data.
Note that images created or pulled for the services remain on your system even after down. If you want to remove unused images too, you can combine Docker level pruning commands, but those operations affect more than just the current Compose project.
Running Specific Services
In many multi container applications you do not always want to start every service. During development, for example, you might only need the web front end and the database, but not a background worker or an optional monitoring component.
Compose allows you to target individual services by naming them after the up command:
docker compose up web db
This starts only the web and db services. Their dependencies might still be started automatically if you have configured dependencies in the Compose file, but optional services that you did not list will stay inactive.
You can combine this with detached mode or rebuild options, such as:
docker compose up -d --build web
which builds and starts only the web service, still within the same project and shared network.
Executing Commands in Running Services
Occasionally you need to run commands inside one of the containers that belong to your multi container application. Instead of identifying the full container name and using low level Docker commands, Compose provides a convenient shortcut:
docker compose exec web sh
This opens a shell session inside the web service container. You can then inspect files, run tests, or check environment variables. When you exit the shell, the container continues running as part of the application.
You can also execute single commands without starting an interactive shell:
docker compose exec db psql -U user -d mydb -c "SELECT 1;"
This is particularly useful in multi container setups where service names double as hostnames between containers. The exec feature keeps you within the context of the Compose project and avoids confusion with other containers that might be running on the same machine.
Scaling Services Horizontally
Multi container applications often benefit from running more than one instance of a particular service. For example, you may want multiple web containers behind a single port mapping to handle more requests. Compose provides a basic scaling mechanism for this.
You can increase the number of containers for a given service using:
docker compose up -d --scale web=3
This runs three containers for the web service inside the same network. Compose uses the same configuration for each replica, and creates distinct container names like project_web_1, project_web_2, and project_web_3.
Other services in the same network still refer to the web service by its name, not by individual container names. Load distribution is typically managed by whichever component is receiving traffic, for example a built in balancing behavior in the default bridge network or an explicit proxy service that you define.
Scaling in Compose is primarily useful for development and simple setups. It helps you test how your application behaves with more than one instance of a service without introducing additional orchestration tools.
Using Profiles to Control Optional Services
In more complex multi container projects, some services are only needed in certain situations. For example, you might have an admin tool or a debugger service that should not run in every environment. Compose supports this idea through profiles, which allow you to group services and enable them selectively.
Services can be marked to belong to specific profiles in the configuration. At runtime, you choose which profiles to enable through environment variables or options when running docker compose up. This way, a single configuration file can represent multiple variants of your multi container application, such as a development stack with extra tools and a leaner stack closer to production.
This approach keeps the definition of your multi container application unified, while still providing flexibility for different users and environments.
Summary of Multi Container Control Flow
Running multi container applications with Docker Compose is largely about managing lifecycle as a group. You define services once, then use docker compose up to start them together, either in the foreground with live logs or in detached mode. You monitor combined logs with docker compose logs, inspect state with docker compose ps, and stop or restart everything with docker compose stop, start, and restart.
When you need to fully clean up, you use docker compose down, potentially with the -v flag if stored data should be removed as well. Throughout this process, you can focus on service names instead of low level container IDs, which simplifies working with your application as it grows beyond a single container.