|
|
How to write a Dockerfile and build a Docker Image
Author: Venkata Sudhakar
A Dockerfile is a plain text file containing a series of instructions that Docker uses to build a container image. Each instruction creates a new layer in the image. When Docker builds an image, it executes each instruction in order and caches the result of each layer. If a layer has not changed since the last build, Docker reuses the cached version, making subsequent builds much faster. Understanding layer caching is essential for writing efficient Dockerfiles. Every Docker image starts from a base image specified by the FROM instruction. For Java applications, popular base images are eclipse-temurin (formerly AdoptOpenJDK), amazoncorretto (Amazon), and openjdk. The key principle for production Dockerfiles is to use the smallest possible base image that contains everything your application needs. Alpine-based images (openjdk:17-alpine) are much smaller than full Debian images, reducing attack surface and pull times. Multi-stage builds take this further by using a large build image to compile the application and then copying only the final artifact into a minimal runtime image. The below example shows a Dockerfile for a Spring Boot application, first as a simple single-stage build and then as an optimised multi-stage build with layer caching for Maven dependencies.
Build and run commands,
# Build the image
docker build -t myapp:1.0.0 .
# Run the container
docker run -d -p 8080:8080 --name myapp myapp:1.0.0
# Check logs
docker logs myapp
# Image size check
docker images myapp
REPOSITORY TAG IMAGE ID SIZE
myapp 1.0.0 abc123def456 285MB
The below example shows an optimised multi-stage Dockerfile that uses Maven to build the application inside Docker and then copies only the compiled JAR into a minimal runtime image, eliminating the need to install Maven locally.
It gives the following output,
# First build (no cache):
Step 1/15: FROM eclipse-temurin:17-jdk-alpine AS builder
Step 4/15: RUN mvn dependency:go-offline -> 45 seconds (downloads 200+ deps)
Step 6/15: RUN mvn package -DskipTests -> 12 seconds
Step 8/15: FROM eclipse-temurin:17-jre-alpine
Successfully built runtime image: 185MB (vs 285MB single-stage)
# Second build after only changing Java source code:
Step 4/15: RUN mvn dependency:go-offline -> CACHED (pom.xml unchanged)
Step 6/15: RUN mvn package -DskipTests -> 8 seconds (only recompiles)
Total build time: 10 seconds (vs 60 seconds first build)
Key Dockerfile best practices: Order instructions from least to most frequently changed - Put COPY pom.xml and RUN mvn dependency:go-offline before COPY src so dependency downloads are cached independently of source changes. Never run as root - Create and use a non-root user. If the application is compromised, the attacker has limited system access. Use .dockerignore - Create a .dockerignore file excluding target/, .git/, node_modules/ and other large directories to speed up the Docker build context transfer. Use specific tags - Never use FROM eclipse-temurin:latest in production. Pin to a specific version like eclipse-temurin:17.0.9_9-jre-alpine for reproducible builds.
|
|