Your First Compose File
A Compose file doesn’t need to start with a complicated application. In fact, the best way to understand Compose is to begin with one container, see how Compose describes it, and only then add more pieces.
The smallest useful Compose file
Let’s start with a container that prints a message and then exits.
Create a directory for the example:
mkdir compose-first
cd compose-firstNow create a file named compose.yaml:
services:
hello:
image: hello-worldThat’s the entire Compose file.
There is no network configuration, no volume, and no environment variable. We are deliberately starting with the smallest useful example.
The structure is simple:
compose.yaml
│
└── services
│
└── hello
│
└── image: hello-worldThe services section describes the containers that make up the application.
Here, we have one service named hello.
The service says:
“Create a container for me using the
hello-worldimage.”
Start the application
From the directory containing compose.yaml, run:
docker compose upCompose reads compose.yaml and creates the resources required by the configuration.
You should see output from the hello-world container, followed by a message indicating that the container has exited.
The important part is not the message itself. What matters is what just happened:
compose.yaml
│
▼
Compose reads configuration
│
▼
Finds the "hello" service
│
▼
Uses the hello-world image
│
▼
Creates and starts the container
│
▼
Container runs and exitsNotice that you didn’t have to manually create a container.
You described the container in the Compose file, and Compose handled the Docker resource for you.
A service is the important building block
The name hello in the file is a service name:
services:
hello:
image: hello-worldThink of a service as the definition of one component of your application.
For this example:
service: hello
│
▼
container created from hello-worldLater, a Compose application might contain several services:
services:
app:
...
database:
...That gives you:
Compose application
│
├── app service
│ └── app container
│
└── database service
└── database containerFor now, though, one service is enough.
compose.yaml is the configuration
You might be tempted to think that docker compose up is the important part.
It isn’t.
The important part is the relationship between the Compose file and the command:
compose.yaml
│
│ describes
▼
application
│
│ managed by
▼
docker compose upThe command doesn’t contain the application’s configuration.
The configuration is in the file.
This is one of the biggest differences from manually typing a sequence of Docker commands.
With manual Docker commands, you might have something like:
Docker command → create this container
Docker command → configure this container
Docker command → start this containerWith Compose, the configuration is stored separately:
compose.yaml
│
▼
"this is what my application needs"
│
▼
docker compose up
│
▼
Compose creates/starts itAdd a second service
One service doesn’t really demonstrate why Compose becomes useful.
Let’s add another container.
Replace compose.yaml with:
services:
hello:
image: hello-world
another:
image: hello-worldNow the application contains two services:
Compose application
│
├── hello
│ └── hello-world container
│
└── another
└── hello-world containerRun:
docker compose upCompose now handles both services as part of the same application.
This is the conceptual jump that Compose is designed for.
Instead of thinking:
“I need to start container A, then container B.”
you can think:
“My application has two services.”
Compose takes care of creating and starting the containers described by those services.
Compose creates an application boundary
When you work with individual Docker commands, it is easy to think only in terms of individual containers.
Compose gives those containers a common application context.
For example:
Compose project
│
┌────────┴────────┐
│ │
hello service another service
│ │
container containerAs the application becomes more realistic, the same idea extends naturally:
Compose project
│
┌────────────┼────────────┐
│ │ │
app database cache
│ │ │
container container containerThe containers are still normal Docker containers.
Compose simply gives you a way to define and manage the collection as one application.
What happens if you run it again?
Compose is not simply a script that blindly executes every line every time.
Run:
docker compose upagain.
Compose examines the current configuration and the resources associated with the project.
This is an important property of declarative configuration:
You describe the desired application, and Compose works to bring the Docker resources into that state.
This becomes much more useful once your application contains long-running services, networks, volumes, and configuration.
You don’t need to understand the entire lifecycle yet. That is the subject of the next lesson.
For now, remember that docker compose up is not just shorthand for a fixed list of Docker commands.
Stop the application
When you’re finished, you can stop the Compose application with:
docker compose downFor this simple example, there is not much to clean up, but the command becomes important as soon as the Compose application contains multiple resources.
Conceptually:
docker compose up
│
▼
Create/start application
│
▼
running
│
▼
docker compose down
│
▼
Remove the application's
managed resourcesYou’ll explore exactly what up and down do in the lifecycle lesson.
A slightly more realistic example
Let’s make the example a little more interesting without introducing networks, volumes, or other concepts yet.
Create this compose.yaml:
services:
first:
image: alpine
second:
image: alpineRun:
docker compose upThe containers will start and then exit because alpine by itself doesn’t have a long-running process to keep the containers alive.
This is actually useful for understanding something important:
Compose manages services; it does not change how containers behave.
If the process inside a container exits, the container exits.
Compose doesn’t turn it into a permanently running container.
A common beginner mistake
You may see examples where someone writes a large Compose file immediately:
services
├── application
├── database
├── cache
├── worker
└── ...That can make Compose look complicated.
Don’t start there.
The core idea is much smaller:
services:
service-name:
image: image-nameEverything else can be added when the application actually needs it.
For example:
services:
hello:
image: hello-worldis enough to introduce the fundamental relationship:
service
│
└── image
│
▼
containerOnce that makes sense, adding more services becomes much easier.
Tip
When learning Compose, don’t memorize a large compose.yaml file. Learn what each section represents and add pieces only when your application needs them.
One more important detail: the service is not the container name
It is tempting to think that this:
services:
hello:means the container itself is named hello.
That is not the right mental model.
hello is the service name in the Compose configuration.
Compose uses the service definition to manage the corresponding container.
For now, think of the relationship as:
Compose configuration
│
▼
hello service
│
▼
managed containerThe service is the configuration-level concept you’ll work with throughout this chapter.
Clean up
If you used the two-service example, stop and remove the Compose application’s resources:
docker compose downYou can then remove the example directory if you no longer need it:
cd ..
rm -rf compose-firstThe exact directory-removal command depends on your operating system. The important Docker cleanup command for this lesson is:
docker compose downWhat you should remember
The first Compose file can be tiny:
services:
hello:
image: hello-worldThe important pieces are:
services
│
└── hello
│
└── imageAnd the basic workflow is:
compose.yaml
│
▼
docker compose up
│
▼
Compose manages the service
│
▼
docker compose downYou don’t need to know networks, volumes, environment variables, or advanced YAML yet.
You’ve established the foundation:
A Compose file describes services, and each service describes a container-based component of the application.
In the next lesson, you’ll look at the Compose lifecycle in more detail: what happens when you bring an application up, stop it, start it again, and take it down.