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
| Language | Slim | Distroless | Scratch |
|---|---|---|---|
| Python | 150MB | 80MB | N/A |
| Node | 80MB | 100MB | N/A |
| Go | 20MB | 15MB | 10MB |
| Rust | 80MB | 15MB | 10MB |
| Java | 200MB | 180MB | N/A |
Common mistakes
- Compiling deps in final image — bloats.
- Static dirs / docs copied in.
:latestbase 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 .