Skip to content

Docker Compose Build: Custom Images & Multi-Stage Builds

DodaTech Updated 2026-06-21 2 min read

Docker Compose can build custom images from Dockerfiles instead of pulling pre-built images, giving you full control over the application environment and dependencies.

What You'll Learn

In this tutorial, you'll learn how to use the build key in Compose, optimize images with multi-stage builds, pass build arguments, and cache layers for faster rebuilds.

Why It Matters

Pre-built images on Docker Hub may not match your exact requirements. Building your own images ensures you control the base OS, package versions, security patches, and application code. Multi-stage builds reduce final image size by separating the build environment from the runtime environment.

Real-World Use

Durga Antivirus Pro's signature update service uses a multi-stage Dockerfile. The first stage compiles the signature compiler from source. The second stage copies only the compiled binary and the latest signature database, producing a final image that is 80 percent smaller than a single-stage build.

Basic Build Configuration

Add a build key instead of image to build from a Dockerfile:

services:
  app:
    build: ./app
    ports:
      - "3000:3000"

This builds the Dockerfile located in the ./app directory.

Custom Dockerfile Name

If your Dockerfile has a different name, specify it:

services:
  app:
    build:
      context: ./app
      dockerfile: Dockerfile.prod

Multi-Stage Builds

A multi-stage Dockerfile uses multiple FROM statements to separate build and runtime:

# Stage 1: Build the application
FROM golang:1.22 AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /app .

# Stage 2: Minimal runtime image
FROM alpine:3.20
RUN apk --no-cache add ca-certificates
COPY --from=builder /app /app
ENTRYPOINT ["/app"]

In Compose, reference the target stage if needed:

services:
  app:
    build:
      context: ./app
      target: <a href="/design-patterns/builder/">Builder</a>

Build Arguments

Pass variables at build time using args:

services:
  app:
    build:
      context: ./app
      args:
        APP_VERSION: "1.2.3"
        NODE_ENV: production

The Dockerfile receives these via ARG:

ARG APP_VERSION
ARG NODE_ENV
RUN echo "Building version $APP_VERSION for $NODE_ENV"

Layer Caching

Docker caches each build layer. Order Dockerfile commands from least to most frequently changing to maximize cache hits:

# Changes rarely
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

# Changes frequently
COPY . .
RUN npm run build

Practice Questions

1. What key replaces image when building from a Dockerfile? The build key.

2. What is the benefit of multi-stage builds? They produce smaller final images by separating build tools from the runtime environment.

3. How do you pass a build-time variable in Compose? Use the args key under build with key-value pairs.

4. Why should you copy package.json before the rest of the source code? To maximize Docker layer Caching -- dependency installation only reruns when dependencies change.

5. Challenge: Create a multi-stage Dockerfile that builds a Rust application in stage one and runs it in a minimal Alpine image. Then reference it from Compose.

Mini Project: Optimized Image Pipeline

Take an existing single-stage Dockerfile and convert it to a multi-stage build. Measure the image size before and after. Create a compose.yml that builds and runs the optimized image. Verify the application works identically.

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro