Docker Containerization Deployment
Docker is a revolution in deployment—build once, run anywhere, and say goodbye to the "it works on my machine" excuse.
1. What You'll Learn
- Multi-stage Dockerfile builds: Builder image + Runtime image (Distroless / Eclipse Temurin)
.dockerignoreOptimization and Mirror Layer Caching Strategies- Docker Compose Orchestration: OrderFlow, MySQL, and Redis—Three Services Working Together
- Environment Variable Injection and Configuration Externalization
- Bob built a production-grade OrderFlow Docker image smaller than 150 MB
2. A True Story of an Operations Engineer
(1) Pain Point: Inconsistent Environment
After Bob finished debugging OrderFlow locally and deployed it to production, the service failed to start due to mismatched MySQL driver versions, inconsistent JDK versions, and differing time zone configurations. Every deployment required half a day to troubleshoot environment discrepancies, and "It works on my machine" became the team's catchphrase. To make matters worse, the three servers required three separate manual deployments, and configurations were often overlooked.
(2) The Docker Solution
Docker packages the application and all its dependencies into a single image:
FROM eclipse-temurin:17-jre-alpine
COPY target/orderflow-service.jar /app/app.jar
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
Deploy with a single command, ensuring a completely consistent environment.
(3) Revenue
After Bob started using Docker, the image was built once locally, and all three servers pulled and ran it from the same source, eliminating environment inconsistencies. A single Docker Compose command starts the complete environment—OrderFlow, MySQL, and Redis—and new hires can get it running locally in just five minutes.
3. Multi-stage Dockerfile
(1) Multi-stage Build Process
graph TD
A["Stage 1: Builder<br/>Maven + JDK 17<br/>Compile + Package"] --> B["orderflow-service.jar"]
B --> C["Stage 2: Runtime<br/>JRE 17 Alpine<br/>Copy JAR only"]
C --> D["Final Image<br/>~150MB"]
(1) ▶ Example: Multi-stage Dockerfile
# Stage 1: Build
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /build
# Copy pom.xml first for dependency caching
COPY pom.xml .
RUN mvn dependency:go-offline -B
# Copy source and build
COPY src ./src
RUN mvn package -DskipTests -B
# Stage 2: Runtime
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
# Create non-root user
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# Copy JAR from builder
COPY --from=builder /build/target/*.jar app.jar
# Set ownership
RUN chown -R appuser:appgroup /app
USER appuser
# Expose port
EXPOSE 8080
# Health check
HEALTHCHECK --interval=30s --timeout=3s \
CMD wget -qO- http://localhost:8080/actuator/health || exit 1
# Run application
ENTRYPOINT ["java", \
"-XX:+UseG1GC", \
"-XX:MaxRAMPercentage=75.0", \
"-jar", "app.jar"]
Output:
// Execution Successful
| Base Image | Size | Security | Use Cases |
|---|---|---|---|
eclipse-temurin:17 |
~450MB | Chinese | Development/Testing |
eclipse-temurin:17-jre-alpine |
~170MB | Medium-High | Recommended for Production |
gcr.io/distroless/java17-debian12 |
~100MB | Maximum | Ultimate Security |
(2) ▶ Example: Distroless image
# Stage 1: Build
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /build
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn package -DskipTests -B
# Stage 2: Distroless runtime
FROM gcr.io/distroless/java17-debian12
COPY --from=builder /build/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
Output:
// Execution Successful
docker exec -it enter debug mode. They are recommended for production, but use the Alpine image for debugging.
4. .dockerignore and Image Optimization
(1) ▶ Example: .dockerignore
# .dockerignore
.git
.github
.idea
*.md
target/
!target/*.jar
node_modules/
*.log
.env
docker-compose*.yml
Dockerfile*
| Optimization Strategy | Effect | Description |
|---|---|---|
.dockerignore Exclude unnecessary files |
Reduce the build context | Do not send .git, .idea, etc. |
| First, COPY pom.xml | Cache dependencies | Skip downloading if dependencies haven't changed |
| Multi-stage build | Reduce the final image size | Does not include Maven or source code |
| JRE instead of JDK | Saves ~200MB | No compilation tools required at runtime |
| Alpine Base Image | Reduced by ~280MB | A minimal Linux system using musl libc |
5. Docker Compose Multi-Service Orchestration
(1) ▶ Example: OrderFlow + MySQL + Redis
# docker-compose.yml
version: "3.9"
services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "8080:8080"
environment:
SPRING_PROFILES_ACTIVE: prod
DB_HOST: mysql
DB_USERNAME: orderflow
DB_PASSWORD: ${DB_PASSWORD:-orderflow123}
REDIS_HOST: redis
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:8080/actuator/health"]
interval: 30s
timeout: 3s
retries: 5
restart: unless-stopped
mysql:
image: mysql:8.0
ports:
- "3306:3306"
environment:
MYSQL_DATABASE: orderflow
MYSQL_USER: orderflow
MYSQL_PASSWORD: ${DB_PASSWORD:-orderflow123}
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:-root123}
volumes:
- mysql-data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 10
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 5
volumes:
mysql-data:
redis-data:
Output:
CONTAINER ID IMAGE STATUS PORTS
abc123 nginx:latest Up 2 hours 0.0.0.0:80->80/tcp
(2) ▶ Example: Startup and Verification
# Build and start all services
docker compose up -d --build
# Check service status
docker compose ps
# View application logs
docker compose logs -f app
# Test API
curl http://localhost:8080/actuator/health
# Stop all services
docker compose down -v
Output:
CONTAINER ID IMAGE STATUS
abc123 latest Up 2 hours
| Command | Description |
|---|---|
docker compose up -d |
Start in the background |
docker compose down |
Stop and delete container |
docker compose logs -f app |
View App Logs |
docker compose ps |
View Service Status |
docker compose build |
Build image only |
6. Environment Variable Injection
(1) ▶ Example: Externalizing Configuration
# application-prod.yml (in Docker image)
spring:
datasource:
url: jdbc:mysql://${DB_HOST:localhost}:3306/orderflow
username: ${DB_USERNAME:orderflow}
password: ${DB_PASSWORD:defaultpass}
data:
redis:
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}
| Configuration Method | Priority | Applicable Scenarios |
|---|---|---|
Docker Compose environment |
High | Easy Deployment |
.env File |
Chinese | Local Development |
| K8s ConfigMap / Secret | High | Production |
# .env file
DB_PASSWORD=secure_password_here
DB_ROOT_PASSWORD=root_secure_here
REDIS_PASSWORD=redis_secure_here
7. Comprehensive Example: Full Dockerization of OrderFlow
# Dockerfile
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /build
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn package -DskipTests -B
FROM eclipse-temurin:17-jre-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY --from=builder /build/target/*.jar app.jar
RUN chown -R appuser:appgroup /app
USER appuser
EXPOSE 8080 8081
HEALTHCHECK --interval=30s --timeout=3s \
CMD wget -qO- http://localhost:8080/actuator/health || exit 1
ENTRYPOINT ["java", "-XX:+UseG1GC", "-XX:MaxRAMPercentage=75.0", "-jar", "app.jar"]
# docker-compose.yml
version: "3.9"
services:
app:
build: .
ports:
- "8080:8080"
- "8081:8081"
environment:
SPRING_PROFILES_ACTIVE: prod
DB_HOST: mysql
DB_USERNAME: orderflow
DB_PASSWORD: ${DB_PASSWORD:-orderflow123}
REDIS_HOST: redis
MANAGEMENT_SERVER_PORT: "8081"
depends_on:
mysql: { condition: service_healthy }
redis: { condition: service_healthy }
restart: unless-stopped
mysql:
image: mysql:8.0
environment:
MYSQL_DATABASE: orderflow
MYSQL_USER: orderflow
MYSQL_PASSWORD: ${DB_PASSWORD:-orderflow123}
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:-root123}
volumes: [mysql-data:/var/lib/mysql]
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s; timeout: 5s; retries: 10
redis:
image: redis:7-alpine
volumes: [redis-data:/data]
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s; timeout: 3s; retries: 5
volumes:
mysql-data:
redis-data:
❓ FAQ
docker compose logs -f app View logs; 2) docker compose exec app sh Enter the container (Alpine image); 3) Remote debugging: ENTRYPOINT ["java", "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005", "-jar", "app.jar"].depends_on and healthcheck?depends_on only ensures the order in which containers start; it does not guarantee that the service is ready. When used in conjunction with condition: service_healthy, Docker Compose will wait for the health check to pass before starting the dependent service.mvn dependency:go-offline to take advantage of Docker layer caching; 2) Use .dockerignore to exclude irrelevant files; 3) Use BuildKit cache mounting.ndots issue). Alpine is recommended for JRE-only scenarios.📖 Summary
- Multi-stage Dockerfile: Compilation during the Builder stage + Runtime stage containing only the JAR and JRE
.dockerignoreExclude irrelevant files; first COPY pom.xml to utilize the cache layer- Docker Compose for multi-service orchestration: app + MySQL + Redis, with health checks to control the startup order
- Configure environment variable injection; store sensitive information in a .env file or a K8s Secret
- For building images, we recommend Eclipse Temurin JRE Alpine (~170 MB) or Distroless (~100 MB)
- Run as a non-root user; HEALTHCHECK is essential for production
📝 Exercises
-
Basic Exercise (Difficulty ⭐): Write a Dockerfile for OrderFlow, build the image, run it locally, and verify that
/actuator/healthreturns "UP." -
Advanced Exercise (Difficulty: ⭐⭐): Write a
docker-compose.ymlfile to orchestrate the three services—OrderFlow, MySQL, and Redis—and configurehealthcheckanddepends_onto ensure the services start in the correct order. -
Challenge (Difficulty: ⭐⭐⭐): Optimize the image to under 150 MB, use the Distroless base image, configure the JVM memory parameters (
MaxRAMPercentage), and add a non-root user and security-related configurations.



