Docker Compose Explained: One File, One Container (2026)

🐳 Docker Compose Explained: One File, One Container (2026)

Quick one-liner: Replace docker run commands with a docker-compose.yml file. One command to start or tear down any container, reproducibly, every time.

🤔 Why This Matter…


This content originally appeared on DEV Community and was authored by David Tio

🐳 Docker Compose Explained: One File, One Container (2026)

Quick one-liner: Replace docker run commands with a docker-compose.yml file. One command to start or tear down any container, reproducibly, every time.

🤔 Why This Matters

In the last post, you connected containers by building a custom bridge network and running CloudBeaver + PostgreSQL by hand:

$ docker network create dtstack
$ docker run -d --rm --name dtpg \
    --network dtstack \
    -e POSTGRES_PASSWORD=docker \
    -e POSTGRES_DB=testdb \
    -v pgdata:/var/lib/postgresql/data \
    --tmpfs /var/run/postgresql \
    postgres:17
$ docker run -d --rm --name cloudbeaver \
    --network dtstack \
    -p 8978:8978 \
    -v cbdata:/opt/cloudbeaver/workspace \
    dbeaver/cloudbeaver:latest

Three commands. That's not the problem.

The problem is:

  • The second command is a 150-character wall of flags
  • One typo in --tmpfs and PostgreSQL silently starts but won't accept connections
  • Forget --network dtstack and the containers won't find each other
  • Tear it down and rebuild? Type it all again
  • What about when you have 5 containers? 10?

There's a better way.

Docker Compose lets you define this entire stack in a single YAML file:

$ docker compose up -d

One command. Same result. Every time.

Here's how it works. Instead of typing flags every time, you write a docker-compose.yml file that captures everything. You list the image, ports, volumes, environment variables, and networks. Then you run docker compose up -d and Docker does the rest. Start it, stop it, tear it down. All with one command.

We'll start by composing each of our containers individually. One compose file for PostgreSQL. One for CloudBeaver. You'll get comfortable with the up/ps/logs/down workflow.

By the end of this post, you'll never have to stare at another never-ending line of docker run flags again.

✅ Prerequisites

  • Ep 1-6 completed. Docker is installed and running, you know volumes, networking, and port mapping. Rootless mode recommended.
  • Docker Compose plugin. Already installed as part of Blog-01/02. Just run docker compose version to verify.

Compose v2: The old docker-compose (with hyphen) is deprecated. Modern Docker ships docker compose (space) as a plugin. If docker compose version doesn't work, go back and re-run the installation steps in Blog-01 or Blog-02. The plugin was included there.

📦 Your First docker-compose.yml

Create a directory for your PostgreSQL service:

$ mkdir -p dtstack-pg && cd dtstack-pg

Create docker-compose.yml:

services:
  dtpg:
    container_name: dtpg
    image: postgres:17
    environment:
      POSTGRES_PASSWORD: docker
      POSTGRES_DB: testdb
    volumes:
      - pgdata:/var/lib/postgresql/data
    tmpfs:
      - /var/run/postgresql

volumes:
  pgdata:

Four things to notice:

  1. services: is the top-level key. Each entry under services: is one container. We have one, and it's called dtpg.

  2. container_name gives it a clean name. Instead of Compose's auto-generated dtstack-pg-dtpg-1, we get dtpg. Same as --name in docker run.

  3. No --network flag. The network is implicit. We're not connecting to anything else yet. One container, one service.

  4. Volumes are declared at the bottom. Named volumes are defined in the volumes: block and referenced by the service. Docker creates them on first use.

🚀 Start the Service

$ docker compose up -d
[+] Running 3/3
 ✔ Network dtstack-pg_default  Created
 ✔ Volume dtstack-pg_pgdata    Created
 ✔ Container dtpg              Started

One command creates a container, a network, and a volume. Everything you need.

Verify it's up:

$ docker compose ps
NAME   IMAGE         COMMAND                  SERVICE   CREATED         STATUS         PORTS
dtpg   postgres:17   "docker-entrypoint.s…"   dtpg      54 seconds ago  Up 54 seconds  5432/tcp

🔍 Inspect the Service

View logs:

$ docker compose logs
dtpg | PostgreSQL init process complete; ready for start up.
dtpg | database system is ready to accept connections

Follow logs in real-time (like docker logs -f):

$ docker compose logs -f

Press Ctrl-C to stop following.

Connect and verify:

$ docker compose exec dtpg psql -U postgres -c "SELECT version();"
                                                      version
--------------------------------------------------------------------------------------------------------------------
 PostgreSQL 17.9 (Debian 17.9-1.pgdg13+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 14.2.0-19) 14.2.0, 64-bit
(1 row)

PostgreSQL is running. We used dtpg to target the container, and Compose knows exactly which one to hit.

Let's bring it down before we make changes:

$ docker compose down
[+] Running 2/2
 ✔ Container dtpg              Removed
 ✔ Network dtstack-pg_default  Removed

The volume survives. Your data is safe.

📁 Using Environment Files

Hardcoding passwords in YAML is bad practice. Move secrets to a .env file:

$ cat > .env << EOF
POSTGRES_PASSWORD=docker
POSTGRES_DB=testdb
EOF

Update docker-compose.yml to reference them:

services:
  dtpg:
    container_name: dtpg
    image: postgres:17
    environment:
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}
    volumes:
      - pgdata:/var/lib/postgresql/data
    tmpfs:
      - /var/run/postgresql

volumes:
  pgdata:

Now docker compose up -d reads the variables automatically. Same command, cleaner file.

🛑 Tear It Down

$ docker compose down
[+] Running 2/2
 ✔ Container dtpg              Removed
 ✔ Network dtstack-pg_default   Removed

The container and network are gone, but the volume survives. Your data is still right where you left it:

$ docker volume ls | grep dtstack
local  dtstack-pg_pgdata

To remove the volume too:

$ docker compose down --volumes
[+] Running 1/1
 ✔ Volume dtstack-pg_pgdata  Removed

Use --volumes when you want a clean slate. Leave it off when you want data to survive across restarts.

📦 Second Compose File: CloudBeaver

Now let's do the same for CloudBeaver. It gets its own directory and its own compose file.

First, go back to your home directory:

$ cd ~

Then create the CloudBeaver directory:

$ mkdir -p dtstack-cb && cd dtstack-cb
services:
  cloudbeaver:
    container_name: cloudbeaver
    image: dbeaver/cloudbeaver:latest
    ports:
      - "8978:8978"
    volumes:
      - cbdata:/opt/cloudbeaver/workspace

volumes:
  cbdata:

Start it:

$ docker compose up -d
[+] Running 3/3
 ✔ Network dtstack-cb_default  Created
 ✔ Volume dtstack-cb_cbdata    Created
 ✔ Container cloudbeaver       Started

Open http://localhost:8978. CloudBeaver loads. ✅

But there's no PostgreSQL on this network. CloudBeaver and PG live in separate compose projects. Different directories, different networks. They can't talk to each other yet.

Déjà vu. We solved this exact problem in the last post with custom bridge networks. Same concept, but this time we're doing it through Compose. We'll get there next post.

For now, let's clean up:

$ docker compose down --volumes

📋 Docker Run vs Docker Compose

Task docker run docker compose
Start docker run -d --name x --network n ... docker compose up -d
List docker ps docker compose ps
Logs docker logs x docker compose logs
Exec docker exec -it x sh docker compose exec x sh
Stop docker stop x docker compose down
Network docker network create Automatic

The docker compose commands are scoped to your project. docker compose ps only shows your stack's containers. It won't list everything running on your machine.

🧪 Exercise: Build Your Nextcloud Stack with Compose

Nextcloud is a self-hosted productivity platform. It functions just like Google Docs, but it runs on your own server. It needs four services: a database, a cache, a web server, and a PHP backend. You'll create four compose files, one per service, each in its own directory.

First, go back to your home directory:

$ cd ~

Part 1: MariaDB

$ mkdir -p nc-db && cd nc-db

Create .env:

$ cat > .env << EOF
MYSQL_ROOT_PASSWORD=nextcloud
MYSQL_DATABASE=nextcloud
MYSQL_USER=nextcloud
MYSQL_PASSWORD=nextcloud
EOF

Create docker-compose.yml:

services:
  db:
    container_name: nc-db
    image: mariadb:11
    ports:
      - "3306:3306"
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
    volumes:
      - dbdata:/var/lib/mysql

volumes:
  dbdata:
$ docker compose up -d

Verify:

$ docker compose exec db mariadb -u root -pnextcloud -e "SHOW DATABASES;"
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| nextcloud          |
| performance_schema |
| sys                |
+--------------------+
$ docker compose down --volumes

Part 2: Redis

$ cd ~
$ mkdir -p nc-redis && cd nc-redis
services:
  redis:
    container_name: nc-redis
    image: redis:8.6
    ports:
      - "6379:6379"
    volumes:
      - redisdata:/data

volumes:
  redisdata:
$ docker compose up -d
$ docker compose exec redis redis-cli PING

You should get PONG.

$ docker compose down --volumes

Part 3: Nextcloud PHP-FPM

$ cd ~
$ mkdir -p nc-php && cd nc-php
services:
  php:
    container_name: nc-php
    image: nextcloud:fpm
    ports:
      - "9000:9000"
    volumes:
      - ./html:/var/www/html
$ docker compose up -d

Nextcloud's PHP-FPM image comes with Nextcloud pre-installed. On first start, it runs its setup scripts and copies the app files into the bind-mounted html/ directory. You can see it populate:

$ ls html/

You'll see Nextcloud's file structure. Things like index.php, core/, apps/, config/. The container put everything there for you.

$ docker compose down

Part 4: Nginx

$ cd ~
$ mkdir -p nc-nginx && cd nc-nginx
services:
  nginx:
    container_name: nc-nginx
    image: nginx:latest
    ports:
      - "8080:80"
    volumes:
      - ./html:/usr/share/nginx/html
$ mkdir -p html
$ cat > html/index.html << 'EOF'
<h2>Nextcloud is coming</h2>
EOF
$ docker compose up -d
$ docker compose exec nginx curl localhost

You should see <h2>Nextcloud is coming</h2>.

$ docker compose down

👉 Coming up: This isn't a full Nextcloud deployment yet, but you now have all the containers you need to get it running. Next post, we'll glue them all up and get it working. See you then.

📚 Want More? This guide covers the basics from Chapter 11: Using Docker Compose in my book, "Levelling Up with Docker". That's 14 chapters of practical, hands-on Docker guides.

> Note: The book has more content than this blog series. Some topics are only available in the book.

📚 Grab the book: "Levelling Up with Docker" on Amazon

Found this helpful? 🙌


This content originally appeared on DEV Community and was authored by David Tio


Print Share Comment Cite Upload Translate Updates
APA

David Tio | Sciencx (2026-04-13T02:18:49+00:00) Docker Compose Explained: One File, One Container (2026). Retrieved from https://www.scien.cx/2026/04/13/docker-compose-explained-one-file-one-container-2026/

MLA
" » Docker Compose Explained: One File, One Container (2026)." David Tio | Sciencx - Monday April 13, 2026, https://www.scien.cx/2026/04/13/docker-compose-explained-one-file-one-container-2026/
HARVARD
David Tio | Sciencx Monday April 13, 2026 » Docker Compose Explained: One File, One Container (2026)., viewed ,<https://www.scien.cx/2026/04/13/docker-compose-explained-one-file-one-container-2026/>
VANCOUVER
David Tio | Sciencx - » Docker Compose Explained: One File, One Container (2026). [Internet]. [Accessed ]. Available from: https://www.scien.cx/2026/04/13/docker-compose-explained-one-file-one-container-2026/
CHICAGO
" » Docker Compose Explained: One File, One Container (2026)." David Tio | Sciencx - Accessed . https://www.scien.cx/2026/04/13/docker-compose-explained-one-file-one-container-2026/
IEEE
" » Docker Compose Explained: One File, One Container (2026)." David Tio | Sciencx [Online]. Available: https://www.scien.cx/2026/04/13/docker-compose-explained-one-file-one-container-2026/. [Accessed: ]
rf:citation
» Docker Compose Explained: One File, One Container (2026) | David Tio | Sciencx | https://www.scien.cx/2026/04/13/docker-compose-explained-one-file-one-container-2026/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.