FROM

The FROM instruction starts every Dockerfile. Learn how to choose the right base image, use variants wisely, and unlock multi-stage builds.

FROM

Every Dockerfile begins with FROM. It sets the foundation that all subsequent instructions build on - the base image your application inherits its OS, runtime, and system libraries from. Choosing well here determines your image size, security surface, and compatibility.

Basic Syntax

FROM <image>
FROM <image>:<tag>
FROM <image>@<digest>
FROM <image>:<tag> AS <name>

Choosing a Base Image

The most impactful decision in any Dockerfile. The wrong choice can mean a 1GB image when 20MB would do.

Official images first

Always start with official images from Docker Hub - they're maintained, regularly patched, and well-documented:

FROM node:20          # Official Node.js
FROM python:3.12      # Official Python
FROM golang:1.22      # Official Go
FROM postgres:16      # Official PostgreSQL
FROM nginx:1.25       # Official Nginx
FROM ubuntu:24.04     # Official Ubuntu

Pick the right variant

Most official images ship in multiple variants. The tag tells you which:

# Full image - everything included, largest size
FROM node:20

# Slim - trimmed system packages, good balance
FROM node:20-slim

# Alpine - musl libc, minimal footprint (~5MB base)
FROM node:20-alpine

# Specific Debian release
FROM node:20-bookworm
FROM node:20-bookworm-slim

Size comparison for Node.js:

TagApprox. SizeNotes
node:20~1.1GBFull Debian, all tools
node:20-slim~240MBDebian, fewer packages
node:20-alpine~135MBAlpine Linux
node:20-alpine + multi-stage~80MBOnly production deps

For production workloads, start with -alpine unless you hit compatibility issues. Some packages with native bindings (e.g. bcrypt, sharp) need glibc and won't work on Alpine without workarounds - use -slim in those cases.

Pin to a specific version

Avoid latest in production - it's a moving target that can break your build unexpectedly:

# Unpredictable - could change tomorrow
FROM node:latest
FROM node:20

#  Predictable - this exact release forever
FROM node:20.11.0
FROM node:20.11.0-alpine3.19

#  Most immutable - pinned to a digest
FROM node:20-alpine@sha256:bf77dc26e48ea95fca9d1aceb5acfa69d2e546b765ec2abfb502975f1a2d4def

For CI/CD pipelines and production images, pin to a full version tag. Reserve latest for local experimentation.

FROM scratch - The Empty Image

scratch is a special pseudo-image meaning "start with nothing":

FROM scratch
COPY myapp /myapp
CMD ["/myapp"]

This is only useful for statically compiled binaries (Go, Rust, C). The result is the smallest possible image - just your binary.

# Go multi-stage example using scratch
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o myapp .

FROM scratch
COPY --from=builder /app/myapp /myapp
ENTRYPOINT ["/myapp"]
# Final image: ~8MB (just the binary)

Multi-Stage FROM

Use multiple FROM statements to separate build and runtime environments. Each FROM starts a new stage:

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

# Stage 2: production (only this stage ships)
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/server.js"]

Naming stages with AS <name> makes COPY --from=<name> readable. You can also reference stages by number (0-indexed), but names are clearer.

Copying From External Images

COPY --from isn't limited to your own stages - you can pull files from any public image:

FROM alpine
# Copy the nginx binary from the official nginx image
COPY --from=nginx:alpine /usr/sbin/nginx /usr/sbin/nginx

This is useful for including a specific tool without making it a full base image dependency.

Platform Targeting

Build images for a specific CPU architecture:

# Explicitly target AMD64 (useful on Apple Silicon Macs)
FROM --platform=linux/amd64 node:20-alpine

# Target ARM64 for Raspberry Pi or AWS Graviton
FROM --platform=linux/arm64 node:20-alpine

Or build multi-platform images with docker buildx:

docker buildx build --platform linux/amd64,linux/arm64 -t myapp:latest .
Exercise

Compare Base Image Sizes

You want to pull both node:20 and node:20-alpine, then compare their sizes side by side. Which sequence of commands is correct?

Keeping Base Images Updated

Even a pinned base image can have vulnerabilities. Rebuild regularly:

# Force pull the latest version of the pinned tag
docker build --pull -t myapp:latest .

# Or update the digest in your Dockerfile periodically
docker pull node:20-alpine
docker inspect node:20-alpine --format='{{index .RepoDigests 0}}'
# → node:20-alpine@sha256:bf77dc26...

In CI, run docker build --pull on a schedule (e.g. weekly) to pick up base image security patches.

Summary

  • FROM sets the entire foundation - it's the most impactful line in your Dockerfile
  • Use official images; prefer -alpine for size, -slim if alpine causes compatibility issues
  • Pin to a specific version tag (or digest) in production - never use latest
  • FROM scratch is for statically compiled binaries only
  • Multi-stage FROM lets you separate build tools from the runtime image - always worth it for compiled languages

Continue Learning

Explore Related Topics

Try the Tool

Related Resources