Master Docker Custom Images: Dockerfile, Compose & Kafka Production Patterns
This tutorial walks through building custom Docker images using Dockerfile, constructing images with docker build, orchestrating multi-service environments with Docker Compose, and customizing middleware images like Kafka for production, including JVM tuning, networking, and data persistence.
Phase 1: Writing a Dockerfile (The Blueprint for Custom Images)
A Dockerfile is a text file composed of fixed instructions that tells Docker how to build your image.
Core Instructions (Must-Know for Beginners)
FROM : Specifies the base image (parent of all images, must be first line)
LABEL : Adds image metadata (author, description, optional)
ENV : Sets environment variables (global effect, convenient for configuration)
RUN : Executes commands during image build (install dependencies, create directories)
COPY : Copies local files into the container (copies only, no extraction)
EXPOSE : Declares container ports (declaration only, no mapping)
ENTRYPOINT : Command executed when container starts (core startup command, cannot be overridden)
CMD : Provides arguments to ENTRYPOINT or acts as standalone startup command (can be overridden)
Production-Ready Generic Template (Java Example)
# 1. Base image (required): grab a ready-made Java runtime from official
FROM openjdk:17-jdk-slim
# 2. Author info (optional)
LABEL maintainer="Lao Tang"
# 3. System params (optional): set container timezone to avoid log time jumps
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 4. Copy files: copy host jar package into container /app directory
# Assume compiled jar in target/ named app.jar
COPY target/*.jar /app/app.jar
# 5. Container startup command (core): what command runs when container starts
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
# 6. Expose port (informational): tells Docker this container needs port 8080
# Note: this is only declaration; external access still needs -p mapping
EXPOSE 8080Three Common Pitfalls for Beginners
Paths must be correct : The path after COPY is relative to the directory where you run the build command.
Don't use ADD : Unless you need auto-extraction of compressed packages, always use COPY for clearer semantics.
Keep images lightweight : The example uses openjdk:17-jdk-slim (slim version), which is much smaller than the full version and deploys faster.
Phase 2: Building the Image (docker build)
After writing the Dockerfile, we "cook" it into an image.
Hands-On Command
Run in your project root directory (the one containing the Dockerfile):
# Format: docker build -t image-name:tag .
# Note: the final dot means use current directory as build context
docker build -t my-spring-boot-app:1.0 .Parameter explanation:
-t : Specifies image name and tag (version), e.g., my-app:v1
. : This dot is critical! It represents the build context — the file directory accessible to the Docker daemon.
Verify Build Result
After successful build, run docker images to see the generated my-spring-boot-app image.
# Run our newly built image
docker run -d -p 8080:8080 --name my-app my-spring-boot-app:1.0Visit http://server-ip:8080; if accessible, you've successfully created your first Docker image.
Phase 3: Docker Compose (For Development Environments)
During development, applications often depend on databases, caches, message queues, etc. Manually running docker run for each container is tedious. Compose solves this: one command starts all services .
⚠️ Note: Compose is typically not used in production. Production requires professional container orchestration platforms like Kubernetes.
Writing docker-compose.yml
Assuming your service needs MySQL, create docker-compose.yml in the project root:
# Specify Compose file version (3.8 is most widely used)
version: '3.8'
# Define services
services:
# Your backend service
backend:
# Image source: the image we just built
image: my-spring-boot-app:1.0
# Container name
container_name: backend-app
# Port mapping
ports:
- "8080:8080"
# Dependency: start db first, then backend
depends_on:
- db
# Environment variables: pass config to backend (e.g., DB connection)
environment:
- SPRING_DATASOURCE_URL=jdbc:mysql://db:3306/mydb?useSSL=false&serverTimezone=UTC
- SPRING_DATASOURCE_USERNAME=root
- SPRING_DATASOURCE_PASSWORD=root
# Restart policy: auto-restart on failure
restart: always
# Database service
db:
# Use official MySQL 8
image: mysql:8.0
container_name: mysql-db
ports:
- "3306:3306"
# Environment variables: initialize root password
environment:
- MYSQL_ROOT_PASSWORD=root
- MYSQL_DATABASE=mydb
# Volume mount: persist data so container deletion doesn't lose data
volumes:
- mysql-data:/var/lib/mysql
restart: always
# Define volumes
volumes:
mysql-data:One-Command Startup
# -d runs in background
docker-compose up -dCommand flow:
Compose auto-reads docker-compose.yml
Pulls/checks db image, starts database
Starts backend service, connects to database
No need to manually run docker run dozens of times
Common Commands
docker-compose up -d # Start in background
docker-compose logs -f # View logs
docker-compose ps # View status
docker-compose down # Stop and remove all containers (keep volumes)
docker-compose down -v # Also remove volumes (use with caution)Phase 4: Production Deployment (Kubernetes Overview)
When services go live, facing hundreds of container instances, rolling updates, auto-scaling, service discovery, etc., Docker Compose no longer suffices. Professional container orchestration platforms are needed; the most mainstream is Kubernetes (K8s).
Typical production workflow:
Each service maintains its own Dockerfile; CI/CD automatically builds images and pushes to image registry (e.g., Docker Hub, Alibaba Cloud ACR).
Write Kubernetes deployment files (Deployment, Service, ConfigMap, etc.) describing how services run, how they're exposed, how they're configured.
Use kubectl or GitOps tools (e.g., ArgoCD) to deploy applications to K8s clusters.
Kubernetes content is vast; this is only a conceptual introduction. For more, see the referenced article.
Phase 5: Production-Grade Image Build Practice: Kafka Example
Beyond Spring Boot, production often requires customizing middleware like Kafka, MySQL, Redis based on official images (modify configs, add monitoring plugins, set default env vars) for unified CI/CD management and distribution.
Why Customize Kafka Image?
The official confluentinc/cp-kafka works out of the box, but real production may need:
Pre-embed custom log4j.properties or server.properties Fix JVM memory parameters to avoid manual specification on every start
Add monitoring agents (e.g., Prometheus JMX Exporter)
Pre-set startup scripts that auto-run initialization on container start
After customization, push to private registry so all environments (dev, test, prod) use identical configuration.
Writing the Dockerfile
Create directory kafka-custom, new Dockerfile (no extension):
# Use official Kafka image as base
FROM confluentinc/cp-kafka:7.5.0
# Set maintainer info
LABEL maintainer="Lao Tang Demo"
# Set timezone (optional, align with business logs)
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# Fix JVM heap size (prevent default values causing OOM)
ENV KAFKA_HEAP_OPTS="-Xmx512M -Xms256M"
# If custom config files needed, prepare and override
# e.g., create config/server.properties with custom config
# COPY config/server.properties /etc/kafka/server.properties
# Expose JMX port for monitoring collection (optional)
EXPOSE 9092 9999
# Official image's entrypoint script handles startup logic; no modification needed hereBuild Custom Image
In the Dockerfile directory: docker build -t my-kafka:7.5.0-custom . After build, docker images shows my-kafka:7.5.0-custom with preset JVM params and timezone.
Manually Start Zookeeper and Kafka
Kafka depends on Zookeeper; start Zookeeper first, then Kafka, on same network.
# 1. Create custom network (enables container-to-container communication)
docker network create kafka-net
# 2. Start Zookeeper (official image)
docker run -d --name zookeeper \
--network kafka-net \
-p 2181:2181 \
-e ZOOKEEPER_CLIENT_PORT=2181 \
confluentinc/cp-zookeeper:7.5.0
# 3. Start our custom Kafka image
docker run -d --name kafka \
--network kafka-net \
-p 9092:9092 \
-e KAFKA_BROKER_ID=1 \
-e KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181 \
-e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092 \
-e KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1 \
my-kafka:7.5.0-customParameter explanations:
--network kafka-net : Places Kafka and Zookeeper in same custom network, allowing access via container names.
-e : Passes environment variables, overriding image defaults (including our preset KAFKA_HEAP_OPTS which remains effective).
KAFKA_ADVERTISED_LISTENERS : For local testing use localhost; if client and Kafka are on different machines, change to host IP.
Verification
# Check container status
docker ps
# Enter Kafka container, test topic creation
docker exec -it kafka bash
# Inside container (path may vary by version)
kafka-topics --bootstrap-server localhost:9092 --create --topic test --partitions 1 --replication-factor 1Successful topic creation confirms custom image and manual deployment work.
Production Recommendations
Config externalization : Parameters like KAFKA_HEAP_OPTS in Dockerfile aid uniformity, but environment-specific configs like KAFKA_ADVERTISED_LISTENERS should be injected via -e at startup for flexibility.
Data persistence : Production must mount volumes to prevent data loss on container restart. Add -v /data/kafka:/var/lib/kafka/data to docker run.
Log collection : Mount Kafka log directory out, or use docker logs with log collection tools.
Multi-node clusters : Single-node is for dev/test only. Production should use Kubernetes StatefulSet for Kafka clusters with persistent storage and Headless Service for high availability.
Closing Thoughts
Docker isn't the destination; it's a key that unlocks the cloud-native world, blurring boundaries between dev, test, and ops, making application delivery as efficient and fun as building LEGO. This tutorial aims to be a stepping stone — go run your first Dockerfile now.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Engineer Tang's Implementation Study Notes
Old Tang | Pseudo-full-stack tech player: writes code without going bald, manages projects without going crazy, delivers without staying up late. No formal tutorials here, only a "slacking-off" practical manual that cures system pain points. Focuses on Shell, SQL, Java, and delivery, turning complexity into simplicity and making efficiency fun.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
