Compose applications

Build a fast development loop

Use targeted rebuilds, logs, exec, and cleanup commands without destroying useful development data by accident.

Compose pays off when the everyday commands become muscle memory. Here are the five I use all the time:

docker compose up --build -d
docker compose logs -f api
docker compose exec api node --version
docker compose ps
docker compose down

up --build -d rebuilds the images that have a build: section, then makes the running project match the file, in the background. logs -f api follows the API output, like tail -f. exec api node --version runs a command inside the already running API container. ps shows the project’s containers with their state and ports. down stops and removes the project’s containers and network.

Know what each change invalidates

Not every edit needs the same command. Match the fix to the layer you changed:

  • Changed server.js? If the source is bind-mounted and a file watcher runs it, the app reloads by itself. In our setup, where the code is copied into the image, you need a rebuild.
  • Changed the Dockerfile or package.json? You need a rebuild. up --build.
  • Changed only compose.yaml, say an environment variable? The image is fine, but the container must be recreated. Plain up -d does that, because Compose notices the config changed.

You can target one service to save time:

docker compose up -d --build api

This rebuilds and recreates only the API, leaving the database alone. Then check docker compose ps and read the CREATED column. Don’t assume the container was replaced. Look.

down versus down -v

docker compose down removes containers and the network. Named volumes stay. Your database data survives, and the next up attaches the same volume. This is the safe reset.

docker compose down -v also removes the project’s named volumes. All the data in them is gone, and there’s no undo. It’s the right command when you want a fresh database, and the wrong one in every other case. I type it slowly.

Debug one layer at a time

When something breaks in development, resist changing three things at once. Change one: the code, the image, or the data. If you suspect the image, tag the last working build before rebuilding, so you can go back:

docker tag notes-api-api notes-api-api:last-good

Compose names built images <project>-<service>, hence notes-api-api. With the old image one tag away, you can tell an application regression apart from a corrupted development database.

Try this: change the text of the note in server.js, rebuild only the API with docker compose up -d --build api, and curl the new response. Then run down followed by up -d and confirm the database still has its data. The volume did its job.

Lesson completed