How to Write a Production-Ready Dockerfile?
A production-ready Dockerfile is not just a file that builds a Docker image. It is a carefully optimized, secure, lightweight, repeatable, and maintainable build definition used to package an application for real production environments.
In real DevOps projects, a Dockerfile should support fast CI/CD builds, small image size, security scanning, predictable deployments, Kubernetes readiness, cloud portability, and easy rollback.
Why Production-Ready Dockerfiles Matter
Many developers write Dockerfiles that work locally but fail in production. A poor Dockerfile can cause security vulnerabilities, large image size, slow builds, unstable containers, memory issues, dependency conflicts, and deployment failures.
Bad Dockerfile Problems:
- Large image size
- Slow build time
- Security vulnerabilities
- Hardcoded secrets
- Runs as root user
- No health check
- Poor caching
- Unstable latest tags
- Unnecessary files copied into image
Production-Ready Dockerfile Goals
- Small image size
- Fast build time
- Secure base image
- No hardcoded secrets
- Non-root container user
- Clear startup command
- Health check support
- Proper dependency caching
- Multi-stage build
- Works in Kubernetes and cloud environments
High-Level Production Dockerfile Flow
Application Source Code
|
v
Dockerfile
|
v
Multi-Stage Build
|
v
Optimized Docker Image
|
v
Security Scan
|
v
Push to Registry
|
v
Deploy to Kubernetes / ECS / EC2
Production-Ready Spring Boot Dockerfile
FROM maven:3.9.6-eclipse-temurin-17 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn clean package -DskipTests
FROM eclipse-temurin:17-jre-jammy
WORKDIR /app
RUN groupadd -r appuser && useradd -r -g appuser appuser
COPY --from=build /app/target/*.jar app.jar
RUN chown -R appuser:appuser /app
USER appuser
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \
CMD curl -f http://localhost:8080/actuator/health || exit 1
ENTRYPOINT ["java", "-jar", "app.jar"]
Important Note About curl in Healthcheck
If your runtime image does not include curl, install it or use another
health check strategy. For Kubernetes production systems, many teams prefer
Kubernetes readiness and liveness probes instead of Dockerfile HEALTHCHECK.
Better Kubernetes-Friendly Spring Boot Dockerfile
FROM maven:3.9.6-eclipse-temurin-17 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn clean package -DskipTests
FROM eclipse-temurin:17-jre-jammy
WORKDIR /app
RUN groupadd -r appuser && useradd -r -g appuser appuser
COPY --from=build /app/target/*.jar app.jar
RUN chown -R appuser:appuser /app
USER appuser
EXPOSE 8080
ENTRYPOINT ["java", "-XX:+UseContainerSupport", "-XX:MaxRAMPercentage=75.0", "-jar", "app.jar"]
Why Use Multi-Stage Build?
Multi-stage builds separate the build environment from the runtime environment. This is one of the most important production Dockerfile practices.
Without Multi-Stage Build
Final Image Contains:
- Maven
- Source code
- Build cache
- Test files
- Temporary files
- Application JAR
With Multi-Stage Build
Final Image Contains:
- Java runtime
- Application JAR
This reduces image size, improves security, and makes deployment faster.
Production Dockerfile Architecture
+------------------------------------------------------+
| Build Stage |
| - Maven |
| - Source code |
| - Dependencies |
| - Package application |
+------------------------------------------------------+
|
v
+------------------------------------------------------+
| Runtime Stage |
| - Lightweight JRE |
| - Application JAR |
| - Non-root user |
| - Startup command |
+------------------------------------------------------+
|
v
+------------------------------------------------------+
| Production Container |
+------------------------------------------------------+
Best Practice 1: Use Specific Base Image Versions
Avoid using latest in production because it can change unexpectedly.
Bad Example
FROM openjdk:latest
Good Example
FROM eclipse-temurin:17-jre-jammy
Specific versions make builds predictable and rollback easier.
Best Practice 2: Use Lightweight Runtime Images
Runtime images should contain only what is required to run the application.
Build Image:
maven:3.9.6-eclipse-temurin-17
Runtime Image:
eclipse-temurin:17-jre-jammy
Use JRE image for running Java applications instead of full JDK when compilation is not needed at runtime.
Best Practice 3: Use .dockerignore
A .dockerignore file prevents unnecessary files from being copied into
Docker build context.
target/
.git/
.idea/
.vscode/
logs/
*.log
node_modules/
.env
*.sql
README.md
This improves build speed and prevents sensitive or unnecessary files from entering the image.
Best Practice 4: Optimize Docker Layer Caching
Docker builds images in layers. Frequently changing files should be copied later.
Bad Example
COPY . .
RUN mvn clean package
Any code change invalidates dependency cache.
Good Example
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn clean package -DskipTests
Dependencies are cached unless pom.xml changes.
Best Practice 5: Do Not Run Containers as Root
Running containers as root is risky in production.
Good Example
RUN groupadd -r appuser && useradd -r -g appuser appuser
RUN chown -R appuser:appuser /app
USER appuser
This limits damage if the application is compromised.
Best Practice 6: Never Store Secrets in Dockerfile
Do not hardcode passwords, API keys, tokens, database credentials, or private keys inside Dockerfiles.
Bad Example
ENV DB_PASSWORD=mysecretpassword
ENV RAZORPAY_SECRET=secret123
Good Approach
Use environment variables from:
- Kubernetes Secrets
- AWS Secrets Manager
- Azure Key Vault
- HashiCorp Vault
- Docker Compose .env file
Best Practice 7: Use ENTRYPOINT Properly
ENTRYPOINT defines the main process of the container.
ENTRYPOINT ["java", "-jar", "app.jar"]
Prefer JSON array format because it handles signals better than shell format.
Bad Example
ENTRYPOINT java -jar app.jar
Good Example
ENTRYPOINT ["java", "-jar", "app.jar"]
Best Practice 8: Use Container-Aware JVM Settings
Java applications running inside Docker should respect container memory limits.
ENTRYPOINT ["java", "-XX:+UseContainerSupport", "-XX:MaxRAMPercentage=75.0", "-jar", "app.jar"]
This helps prevent memory issues in Kubernetes, Docker Compose, ECS, and cloud deployments.
Best Practice 9: Keep Containers Stateless
Production containers should not store important data inside the container filesystem.
Bad Approach
Store uploaded files inside container
Store database data inside container
Store logs only inside container
Good Approach
Uploads -> S3 / external volume
Database -> MySQL / PostgreSQL volume
Logs -> Loki / ELK / CloudWatch
Sessions -> Redis
Best Practice 10: Add Health Checks Carefully
Health checks help detect unhealthy containers.
HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \
CMD curl -f http://localhost:8080/actuator/health || exit 1
For Spring Boot, expose actuator health endpoint:
management.endpoints.web.exposure.include=health,info
management.endpoint.health.probes.enabled=true
Best Practice 11: Use EXPOSE as Documentation
EXPOSE does not publish the port automatically. It documents which port the container application listens on.
EXPOSE 8080
Actual port mapping happens using Docker run, Docker Compose, or Kubernetes Service.
Best Practice 12: Use Image Versioning
Avoid deploying unversioned images in production.
Bad
payment-service:latest
Good
payment-service:1.0.0
payment-service:2026.05.24
payment-service:git-commit-sha
Versioned images make rollback simple and predictable.
Production CI/CD Flow
Developer Pushes Code
|
v
Run Unit Tests
|
v
Build Docker Image
|
v
Scan Image
|
v
Push to Registry
|
v
Deploy to Staging
|
v
Smoke Test
|
v
Deploy to Production
|
v
Monitor Logs and Metrics
Real-Time Production Example
Assume a career learning platform has these services:
- api-gateway
- portfolio-service
- interview-service
- payment-service
- notification-service
- assessment-service
Each service should have a separate production Dockerfile.
api-gateway/Dockerfile
portfolio-service/Dockerfile
interview-service/Dockerfile
payment-service/Dockerfile
notification-service/Dockerfile
assessment-service/Dockerfile
Production Dockerfile for API Gateway
FROM maven:3.9.6-eclipse-temurin-17 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn clean package -DskipTests
FROM eclipse-temurin:17-jre-jammy
WORKDIR /app
RUN groupadd -r appuser && useradd -r -g appuser appuser
COPY --from=build /app/target/*.jar app.jar
RUN chown -R appuser:appuser /app
USER appuser
EXPOSE 9090
ENTRYPOINT ["java", "-XX:+UseContainerSupport", "-XX:MaxRAMPercentage=75.0", "-jar", "app.jar"]
Production Dockerfile for Any Spring Boot Microservice
FROM maven:3.9.6-eclipse-temurin-17 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn clean package -DskipTests
FROM eclipse-temurin:17-jre-jammy
WORKDIR /app
RUN groupadd -r appuser && useradd -r -g appuser appuser
COPY --from=build /app/target/*.jar app.jar
RUN chown -R appuser:appuser /app
USER appuser
EXPOSE 8080
ENTRYPOINT ["java", "-XX:+UseContainerSupport", "-XX:MaxRAMPercentage=75.0", "-jar", "app.jar"]
Docker Compose Example for Production-Like Deployment
services:
api-gateway:
image: dhanishempower/api-gateway:1.0.0
ports:
- "9090:9090"
environment:
SPRING_PROFILES_ACTIVE: prod
PORTFOLIO_SERVICE_URL: http://portfolio-service:8080
INTERVIEW_SERVICE_URL: http://interview-service:8082
PAYMENT_SERVICE_URL: http://payment-service:8084
depends_on:
- portfolio-service
- interview-service
- payment-service
restart: unless-stopped
portfolio-service:
image: dhanishempower/portfolio-service:1.0.0
environment:
SPRING_PROFILES_ACTIVE: prod
DB_URL: jdbc:mysql://mysql:3306/portfolio_db
DB_USERNAME: root
DB_PASSWORD: ${DB_PASSWORD}
restart: unless-stopped
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
volumes:
- mysql-data:/var/lib/mysql
restart: unless-stopped
volumes:
mysql-data:
Production Security Checklist
| Checklist Item | Why It Matters |
|---|---|
| Use non-root user | Reduces container privilege risk |
| Do not hardcode secrets | Prevents credential leakage |
| Use official base image | Improves trust and maintainability |
| Use specific image tag | Prevents unexpected changes |
| Use image scanning | Detects vulnerabilities |
| Use .dockerignore | Prevents unnecessary/sensitive files |
Common Dockerfile Mistakes
- Using
latesttag in production - Running application as root
- Copying entire project before dependency caching
- Hardcoding database passwords
- Building huge images with unnecessary tools
- Not using multi-stage builds
- Storing logs and uploads inside container only
- Ignoring JVM container memory settings
- Missing .dockerignore file
Bad Dockerfile Example
FROM openjdk:latest
COPY . /app
WORKDIR /app
RUN mvn clean package
ENV DB_PASSWORD=root
EXPOSE 8080
CMD java -jar target/app.jar
Problems in This Dockerfile
- Uses latest tag
- No multi-stage build
- Copies unnecessary files
- Hardcoded secret
- Runs as root
- Uses shell form CMD
- Large final image
Good Production Dockerfile Example
FROM maven:3.9.6-eclipse-temurin-17 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn clean package -DskipTests
FROM eclipse-temurin:17-jre-jammy
WORKDIR /app
RUN groupadd -r appuser && useradd -r -g appuser appuser
COPY --from=build /app/target/*.jar app.jar
RUN chown -R appuser:appuser /app
USER appuser
EXPOSE 8080
ENTRYPOINT ["java", "-XX:+UseContainerSupport", "-XX:MaxRAMPercentage=75.0", "-jar", "app.jar"]
Interview Answer
A production-ready Dockerfile should create a lightweight, secure, stable, and optimized Docker image. It should use multi-stage builds, specific base image versions, a non-root user, proper caching, .dockerignore, no hardcoded secrets, and a clean ENTRYPOINT.
For Java Spring Boot applications, the build stage can use Maven with JDK, while the runtime stage should use a smaller JRE image. This reduces image size, improves security, and makes deployments faster in Docker, Kubernetes, AWS ECS, or any cloud platform.
Production Dockerfile Review Checklist
Before Production Deployment, Check:
[ ] Is image tag specific?
[ ] Is multi-stage build used?
[ ] Is final image small?
[ ] Is app running as non-root user?
[ ] Are secrets removed from Dockerfile?
[ ] Is .dockerignore added?
[ ] Is ENTRYPOINT in JSON format?
[ ] Are JVM container settings added?
[ ] Is image scanned for vulnerabilities?
[ ] Are logs going to stdout/stderr?
[ ] Is persistent data stored outside container?
Useful Internal Links
- Docker Interview Questions
- DevOps Interview Questions
- Kubernetes Interview Questions
- Microservices Interview Questions
- AWS Interview Questions
- Spring Boot Interview Questions
Final Conclusion
Writing a production-ready Dockerfile is a critical DevOps skill. A good Dockerfile improves build speed, deployment reliability, application security, cloud portability, and production stability.
In real production systems, always prefer multi-stage builds, lightweight runtime images, non-root users, versioned images, externalized secrets, proper caching, and container-aware runtime settings. This makes Docker images reliable for microservices, Kubernetes, CI/CD pipelines, and enterprise cloud deployments.