docker
docker-compose
image naming
containerization
DevOps

How do I define the name of image built with docker-compose

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

To name an image built with Docker Compose, add the image key alongside the build key in your service definition. When both are present, Compose builds the image from the specified Dockerfile and tags it with the name you provide in image. Without an explicit image key, Compose auto-generates a name using the pattern {project_name}-{service_name}, which is often unclear in image listings and CI pipelines.

Default Image Naming Behavior

When you only specify build without image, Docker Compose constructs the image name automatically:

yaml
services:
  webapp:
    build: .

Running docker compose build produces an image named {project}-webapp, where {project} defaults to the directory name containing your compose.yaml file. If the directory is called my-app, the image becomes my-app-webapp.

This default name has several problems in practice:

  • It changes if you move the project to a different directory
  • It is not suitable for pushing to a container registry
  • It makes docker images output harder to scan when you have many projects

Setting an Explicit Image Name

Add the image key to your service to control the name:

yaml
1services:
2  webapp:
3    build:
4      context: .
5      dockerfile: Dockerfile
6    image: mycompany/webapp:1.0.0

When Compose builds this service, it tags the resulting image as mycompany/webapp:1.0.0. You can then push it directly to a registry without re-tagging:

bash
docker compose build webapp
docker push mycompany/webapp:1.0.0

Name Format Rules

Docker image names must follow specific conventions:

RuleExampleValid?
Lowercase onlymyappYes
With namespacemycompany/myappYes
With registryghcr.io/mycompany/myappYes
With tagmyapp:v2.1Yes
Uppercase lettersMyAppNo
Spacesmy appNo
Special characters (except -, _, ., /)my@appNo

If no tag is specified, Docker defaults to :latest.

Multiple Services with Custom Names

Each service can have its own image name. This is essential for multi-service applications where you push individual components to a registry:

yaml
1services:
2  api:
3    build:
4      context: ./api
5      dockerfile: Dockerfile
6    image: mycompany/api:1.0.0
7
8  worker:
9    build:
10      context: ./worker
11      dockerfile: Dockerfile
12    image: mycompany/worker:1.0.0
13
14  frontend:
15    build:
16      context: ./frontend
17      dockerfile: Dockerfile.prod
18    image: mycompany/frontend:1.0.0

Running docker compose build builds all three images with distinct, registry-ready names.

Using Environment Variables for Dynamic Names

Hardcoding version tags in your compose file means editing the file for every release. Environment variables solve this:

yaml
1services:
2  webapp:
3    build:
4      context: .
5    image: "${REGISTRY:-ghcr.io}/${IMAGE_NAME:-mycompany/webapp}:${TAG:-latest}"

You can then control the image name at build time:

bash
1# Uses defaults: ghcr.io/mycompany/webapp:latest
2docker compose build
3
4# Override for a specific release
5REGISTRY=ecr.aws TAG=v2.3.1 docker compose build

Using a .env File

For team-shared defaults, put the variables in a .env file alongside your compose file:

bash
1# .env
2REGISTRY=ghcr.io
3IMAGE_NAME=mycompany/webapp
4TAG=latest

Docker Compose reads .env automatically. Individual developers or CI jobs can override these values through environment variables or by specifying an alternate env file:

bash
docker compose --env-file .env.production build

The image Key Without build

When you specify image without build, Compose pulls the image from a registry instead of building it:

yaml
1services:
2  database:
3    image: postgres:16-alpine
4    ports:
5      - "5432:5432"

This is how you reference pre-built images. The key distinction:

ConfigurationBehavior
build onlyBuilds and auto-names the image
image onlyPulls from registry
build + imageBuilds and tags with the specified name

Controlling the Project Name

Since the default image name includes the project name, you can also influence it by setting the project name explicitly:

yaml
1# compose.yaml
2name: myproject
3
4services:
5  webapp:
6    build: .

This produces myproject-webapp instead of using the directory name. You can also set it via the command line:

bash
docker compose -p myproject build

Or through the COMPOSE_PROJECT_NAME environment variable:

bash
export COMPOSE_PROJECT_NAME=myproject
docker compose build

However, this only changes the auto-generated prefix. For registry-ready names, always use the image key explicitly.

Build and Push Workflow

A complete CI workflow for building, naming, and pushing images:

yaml
1# compose.yaml
2services:
3  api:
4    build:
5      context: .
6      dockerfile: Dockerfile
7      args:
8        BUILD_DATE: "${BUILD_DATE}"
9        GIT_SHA: "${GIT_SHA}"
10    image: "ghcr.io/mycompany/api:${TAG:-latest}"
bash
1#!/usr/bin/env bash
2set -euo pipefail
3
4export TAG="${GITHUB_SHA:0:8}"
5export BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ)
6export GIT_SHA="${GITHUB_SHA}"
7
8docker compose build api
9docker compose push api

The docker compose push command pushes the image using the name defined in the image key. Without that key, there is no registry-compatible name to push.

Compose V1 vs V2

The naming behavior differs slightly between Compose V1 (docker-compose) and Compose V2 (docker compose):

AspectV1 (docker-compose)V2 (docker compose)
Default separator_ (underscore)- (hyphen)
Default name{dir}_{service}{dir}-{service}
version keyRequiredOptional (ignored)
Command syntaxdocker-compose builddocker compose build

If you are migrating from V1 to V2 and scripts depend on the auto-generated image name, the separator change can break things. Using an explicit image key avoids this problem entirely.

Note: the version key (like version: '3.8') is no longer required in Compose V2 and is officially deprecated. You can safely remove it from your compose files.

Common Pitfalls

Forgetting to include both build and image together is the most common mistake. With only build, the image gets an auto-generated name that is not useful for pushing to a registry. With only image, Compose tries to pull from a registry instead of building locally.

Relying on the auto-generated project name makes image names fragile. Renaming or moving the project directory changes the image name, which can silently break deployment scripts.

Using uppercase letters in image names causes Docker to reject the tag. All image name components must be lowercase.

Hardcoding version tags in the compose file means editing YAML for every release. Use environment variables with sensible defaults instead.

Not specifying a tag defaults to :latest, which is problematic for production because it is mutable and does not identify a specific version. Always tag releases with a version number or commit hash.

Forgetting that Compose V1 and V2 use different separators (_ vs -) in auto-generated names can break scripts that parse image names.

Summary

  • Add the image key alongside build to name your built image explicitly.
  • Without image, Compose auto-generates a name from the project and service name, which is fragile and not registry-ready.
  • Use environment variables with defaults for dynamic tagging in CI/CD pipelines.
  • Each service in a multi-service compose file can have its own image name.
  • Prefer explicit image names over relying on project name conventions, especially when pushing to registries.
  • The version key in compose files is deprecated in Compose V2 and can be removed.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.