← Back to Questions
Docker

How to write a production-ready Dockerfile?

Learn How to write a production-ready Dockerfile? with simple explanations, real-time examples, interview tips and practical use cases.

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.

Simple Definition: A production-ready Dockerfile builds a secure, small, fast, stable, and reusable Docker image suitable for running applications in production environments.

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 latest tag 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

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.

Why this Docker question is important?

This interview question helps candidates understand real-time backend development concepts, practical problem solving, coding fundamentals, system design basics and production-ready application behavior.

Practice this question carefully for Java backend roles, Spring Boot developer interviews, microservices interviews, company interviews and full-stack developer preparation.

About the Author

Naresh Kumar is a Senior Java Backend Engineer with experience building enterprise applications using Java, Spring Boot, Microservices, Docker, Kubernetes and Cloud technologies.