Slim images per language.

Python (uv)

FROM python:3.13-slim AS builder
WORKDIR /app
RUN pip install uv
COPY pyproject.toml uv.lock ./
RUN --mount=type=cache,target=/root/.cache/uv \
    uv sync --frozen --no-dev

FROM python:3.13-slim
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends libpq5 \
 && rm -rf /var/lib/apt/lists/*
RUN useradd -u 1000 -m app
COPY --from=builder --chown=app:app /app/.venv ./.venv
COPY --chown=app:app . .
ENV PATH=/app/.venv/bin:$PATH PYTHONUNBUFFERED=1
USER app
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0"]

~150MB.

Python (distroless)

FROM python:3.13-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --target=/app/deps -r requirements.txt

FROM gcr.io/distroless/python3-debian12
WORKDIR /app
COPY --from=builder /app/deps /app/deps
COPY . .
ENV PYTHONPATH=/app/deps
USER nonroot
CMD ["main.py"]

~80MB. No shell — use :debug variant for debugging.

Node.js

FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev

FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-alpine
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY package*.json ./
USER node
EXPOSE 3000
CMD ["node", "dist/server.js"]

~80MB.

Next.js (standalone)

FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

FROM node:20-alpine
WORKDIR /app
RUN addgroup -g 1001 -S nodejs && adduser -S next -u 1001
COPY --from=builder --chown=next:nodejs /app/.next/standalone ./
COPY --from=builder --chown=next:nodejs /app/.next/static ./.next/static
COPY --from=builder --chown=next:nodejs /app/public ./public
USER next
EXPOSE 3000
ENV PORT=3000 HOSTNAME=0.0.0.0
CMD ["node", "server.js"]

~150MB (with standalone output).

Go (scratch)

FROM golang:1.22-alpine AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/root/.cache/go-build \
    go mod download
COPY . .
RUN --mount=type=cache,target=/root/.cache/go-build \
    CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /out/app .

FROM scratch
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=build /out/app /app
ENTRYPOINT ["/app"]

~10-20MB.

Go (distroless)

FROM gcr.io/distroless/static-debian12
COPY --from=build /out/app /app
USER nonroot
ENTRYPOINT ["/app"]

Adds DNS resolution and timezone data over scratch.

Rust

FROM rust:1.78 AS build
WORKDIR /src
RUN apt-get update && apt-get install -y musl-tools && \
    rustup target add x86_64-unknown-linux-musl
COPY Cargo.toml Cargo.lock ./
RUN mkdir src && echo "fn main(){}" > src/main.rs && \
    cargo build --release --target x86_64-unknown-linux-musl
RUN rm -rf src
COPY . .
RUN cargo build --release --target x86_64-unknown-linux-musl

FROM scratch
COPY --from=build /src/target/x86_64-unknown-linux-musl/release/myapp /app
ENTRYPOINT ["/app"]

~10MB.

Java

FROM eclipse-temurin:21-jdk AS build
WORKDIR /src
COPY . .
RUN ./gradlew bootJar

FROM eclipse-temurin:21-jre-alpine
COPY --from=build /src/build/libs/*.jar /app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app.jar"]

Or jlink for smaller:

FROM eclipse-temurin:21 AS build
RUN jlink --add-modules java.base,java.logging \
          --strip-debug --no-man-pages --no-header-files \
          --compress=2 --output /jre

FROM debian:12-slim
COPY --from=build /jre /jre
COPY app.jar /app.jar
ENV PATH=/jre/bin:$PATH
ENTRYPOINT ["java", "-jar", "/app.jar"]

Ruby

FROM ruby:3.3-slim AS build
WORKDIR /app
RUN apt-get update && apt-get install -y build-essential libpq-dev
COPY Gemfile* ./
RUN bundle install --jobs 4 --deployment --without development test

FROM ruby:3.3-slim
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends libpq5 \
 && rm -rf /var/lib/apt/lists/*
COPY --from=build /app/vendor/bundle /app/vendor/bundle
COPY . .
ENV BUNDLE_PATH=/app/vendor/bundle
USER 1000:1000
EXPOSE 3000
CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]

C++

FROM debian:12 AS build
RUN apt-get update && apt-get install -y g++ make cmake
WORKDIR /src
COPY . .
RUN cmake -B build && cmake --build build

FROM debian:12-slim
COPY --from=build /src/build/myapp /app
ENTRYPOINT ["/app"]

For musl-static C++:

FROM alpine:3.20 AS build
RUN apk add --no-cache g++ make cmake
...
RUN cmake -DCMAKE_EXE_LINKER_FLAGS="-static" -B build && cmake --build build

FROM scratch
COPY --from=build /src/build/myapp /app
ENTRYPOINT ["/app"]

PHP

FROM composer:2 AS deps
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-scripts --no-autoloader

FROM php:8.3-fpm-alpine
WORKDIR /var/www
COPY --from=deps /app/vendor ./vendor
COPY . .
RUN composer dump-autoload --optimize
USER www-data
EXPOSE 9000
CMD ["php-fpm"]

Sizes summary

LanguageSlimDistrolessScratch
Python150MB80MBN/A
Node80MB100MBN/A
Go20MB15MB10MB
Rust80MB15MB10MB
Java200MB180MBN/A

Common mistakes

  • Compiling deps in final image — bloats.
  • Static dirs / docs copied in.
  • :latest base image — non-reproducible.
  • No multi-stage → 1GB images.
  • Running as root.

Read this next

If you want my multi-language Dockerfile templates, they’re at rajpoot.dev .


Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .