Table of Contents
- Course Overview
- Getting Started with Docker for Java
- Building Java Applications with Dockerfiles
- Building with Build Tools and Plugins
- Running Multi-container Apps with Docker Compose
- Configuring Java Applications in Containers
- Managing Application Logs with Docker
- IDE Integration — IntelliJ & VS Code
- Debugging Java Applications in Containers
- Quick Reference
1. Course Overview
This course covers using Docker for Java application development. Main topics covered:
- Using Dockerfiles, Maven/Gradle images, and multi-stage builds
- Maven/Gradle plugins for building Docker images
- Managing multi-container applications with Docker Compose
- Configuring applications via environment variables, properties files,
ENTRYPOINT/CMD - Docker logging model and multiline log handling (stack traces)
- Docker integration in IntelliJ IDEA and Visual Studio Code
- Remote debugging of Java applications running in containers
Prerequisites: Basic knowledge of Java and Docker.
2. Getting Started with Docker for Java
Core Docker Concepts
Docker allows creating isolated environments (containers) that bundle an application and all its dependencies. The Java analogy:
| Docker Concept | Java Analogy |
|---|---|
Image | Class |
Container | Object (instance) |
Dockerfile | Class source code |
Docker Hub | Maven Central / Nexus |
Layer | Filesystem snapshot at a point in time |
Docker Architecture
graph TD
A[Developer] -->|docker build| B[Dockerfile]
B --> C[Image]
C -->|docker run| D[Container 1]
C -->|docker run| E[Container 2]
C -->|docker push| F[Docker Hub / Registry]
F -->|docker pull| G[Prod Server]
G -->|docker run| H[Container Prod]
subgraph "Build Context"
B
I[Source Code]
J[JAR / WAR]
end
subgraph "Layers Cache"
K[Layer: Base OS]
L[Layer: JRE/JDK]
M[Layer: App dependencies]
N[Layer: App code]
end
C --> K
K --> L
L --> M
M --> N
Getting Started Commands
# Compile a Java file in a container (without installing Java locally)
docker run -v $(pwd):/hello -w /hello amazoncorretto:17 javac Hello.java
# Execute the compiled class
docker run -v $(pwd):/hello -w /hello amazoncorretto:17 java Hello
# List local images
docker images
# List running containers
docker ps
# List all containers (including stopped ones)
docker ps -a
# Remove a container
docker rm <container_id>
# Remove an image
docker rmi <image_name>
Hello.java — minimal example
public class Hello {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
3. Building Java Applications with Dockerfiles
Choosing a Base Image
Choosing a base image is one of the most important decisions. The official openjdk image is deprecated.
| Image | Publisher | Notable feature | Recommendation |
|---|---|---|---|
eclipse-temurin | Eclipse Adoptium | TCK-certified OpenJDK, multi-architecture | ✅ Recommended |
amazoncorretto | Amazon | Security + perf patches, free LTS support | ✅ Good option |
bellsoft/liberica | BellSoft | Debian / Alpine variants | ✅ Viable |
openjdk | OpenJDK | Deprecated — do not use | ❌ Deprecated |
java | Docker Library | Deprecated — do not use | ❌ Deprecated |
| Oracle JDK | Oracle | Restrictive license, public distribution prohibited | ⚠️ License caution |
Available variants for Eclipse Temurin:
- Default: standard image, most complete
- Alpine: very lightweight (~50% smaller), but without Git/Bash
- Windows Server Core: for Windows environments
Dockerfile for a JAR Application
# Base image: Eclipse Temurin Java 21
FROM eclipse-temurin:21
# Create application directory
RUN mkdir /app
# Set the working directory (created if absent)
WORKDIR /app
# Copy the compiled JAR (COPY preferred over ADD)
COPY target/service.jar app.jar
# Document the exposed port (informational only)
EXPOSE 8080
# Start command — exec form (recommended)
ENTRYPOINT ["java", "-jar", "app.jar"]
Dockerfile for a WAR Application (Tomcat)
FROM tomcat:11.0
# ARG: variable available only at build time
ARG OUTDIR=build/libs
# Copy the WAR into the Tomcat webapps directory
COPY ${OUTDIR}/web.war ${CATALINA_HOME}/webapps/ROOT.war
EXPOSE 8080
# CMD can be overridden (unlike ENTRYPOINT)
CMD ["catalina.sh", "run"]
Dockerfile with Maven/Gradle Images
# Use the official Maven image to compile
FROM maven:3.9-eclipse-temurin-21 AS maven-build
WORKDIR /app
# Copy pom.xml first to optimize dependency caching
COPY pom.xml .
RUN mvn dependency:resolve
# Copy sources (only invalidates cache if sources change)
COPY src ./src
RUN mvn package -DskipTests
# Execution stage: lightweight image
FROM tomcat:11.0
COPY --from=maven-build /app/target/web.war ${CATALINA_HOME}/webapps/ROOT.war
EXPOSE 8080
CMD ["catalina.sh", "run"]
Multi-stage Builds
A multi-stage build separates the compilation environment from the execution environment, avoiding including the JDK, Maven/Gradle, or sources in the final image.
graph LR
subgraph "Stage 1: build"
A[maven:3.9-eclipse-temurin-21]
B[pom.xml + src/]
C[target/web.war]
A --> B --> C
end
subgraph "Stage 2: runtime"
D[tomcat:11.0]
E[webapps/ROOT.war]
F[Lightweight final image]
D --> E --> F
end
C -->|COPY --from=build| E
# Stage 1: Build with Maven
FROM maven:3.9-eclipse-temurin-21 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:resolve
COPY src ./src
RUN mvn package -DskipTests
# Stage 2: Runtime with Tomcat (build artifacts discarded)
FROM tomcat:11.0
COPY --from=build /app/target/web.war ${CATALINA_HOME}/webapps/ROOT.war
EXPOSE 8080
CMD ["catalina.sh", "run"]
Multi-stage Build — JAR (Maven)
# Build stage
FROM maven:3.9-eclipse-temurin-21 AS build
WORKDIR /app
COPY pom.xml .
COPY src ./src
RUN mvn package -DskipTests
# Runtime stage
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=build /app/target/service.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
BuildKit Cache Mount for Maven
Avoids re-downloading all Maven dependencies on every build, even when pom.xml changes:
FROM maven:3.9-eclipse-temurin-21 AS build
WORKDIR /app
COPY pom.xml .
COPY src ./src
# Persistent Maven local repo (~/.m2) cache managed by BuildKit
RUN --mount=type=cache,target=/root/.m2 mvn package -DskipTests
FROM tomcat:11.0
COPY --from=build /app/target/web.war ${CATALINA_HOME}/webapps/ROOT.war
EXPOSE 8080
CMD ["catalina.sh", "run"]
# Manually clear the BuildKit cache
docker buildx prune
ENTRYPOINT vs CMD
| Instruction | Form | Behavior | Override with docker run |
|---|---|---|---|
ENTRYPOINT | exec / shell | Main container command | --entrypoint flag |
CMD | exec / shell | Default arguments or default command | Arguments after the image |
Usage rules:
- Define at least
CMDorENTRYPOINT ENTRYPOINTfor a container used as an executableCMDfor default arguments to anENTRYPOINTCMDis easily overridable — preferCMDwhen flexibility is needed
# Typical combination for Spring Boot JAR
ENTRYPOINT ["java"]
CMD ["-jar", "app.jar"]
# Override: docker run myimage -jar app.jar --spring.profiles.active=prod
4. Building with Build Tools and Plugins
Fabric8 Docker Maven Plugin
Maven plugin that communicates directly with the Docker Engine to build images and manage containers, without leaving the Maven ecosystem.
Main goals:
| Maven Goal | Default phase | Description |
|---|---|---|
docker:build | package | Builds the Docker image |
docker:start / docker:run | pre-integration-test | Creates and starts the container |
docker:stop | post-integration-test | Stops the container |
docker:push | deploy | Pushes the image to a registry |
Configuration in pom.xml:
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>0.43.4</version>
<configuration>
<images>
<image>
<name>my-api-service</name>
<build>
<!-- If a Dockerfile is present at the root, no XML config needed -->
<contextDir>${project.basedir}</contextDir>
</build>
<run>
<ports>
<port>8080:8080</port>
</ports>
</run>
</image>
</images>
</configuration>
</plugin>
Gradle Docker Plugin (Benjamin Muschko)
Three distinct plugins:
| Plugin | Use case |
|---|---|
| Remote API Plugin | Interaction with the remote Docker API |
| Java Application Plugin | Build and push images for standard Java apps |
| Spring Boot Application Plugin | Build and push images for Spring Boot apps |
Java Application Plugin tasks:
| Task | Description |
|---|---|
dockerSyncBuildContext | Copies files to the temp directory |
dockerCreateDockerfile | Generates the Dockerfile automatically |
dockerBuildImage | Builds the image |
dockerPushImage | Pushes the image to the registry |
Spring Boot Layered JARs
Spring Boot 2.3+ allows creating layered JARs to optimize Docker cache:
BOOT-INF/
├── classes/ → application code (changes often)
│ └── com/...
├── lib/ → dependencies (changes rarely)
│ └── *.jar
└── classpath.idx
META-INF/
org/springframework/boot/loader/
layers.xml configuration:
<layers xmlns="http://www.springframework.org/schema/boot/layers"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<application>
<into layer="spring-boot-loader">
<include>org/springframework/boot/loader/**</include>
</into>
<into layer="application"/>
</application>
<dependencies>
<into layer="snapshot-dependencies">
<include>*:*:*SNAPSHOT</include>
</into>
<into layer="dependencies"/>
</dependencies>
<layerOrder>
<layer>dependencies</layer>
<layer>spring-boot-loader</layer>
<layer>snapshot-dependencies</layer>
<layer>application</layer>
</layerOrder>
</layers>
Optimized Dockerfile for Spring Boot layered JAR:
FROM eclipse-temurin:21 AS builder
WORKDIR /app
COPY target/service.jar app.jar
# Extract JAR layers
RUN java -Djarmode=layertools -jar app.jar extract
FROM eclipse-temurin:21-jre
WORKDIR /app
# Copy in layer order (least frequently changed first)
COPY --from=builder /app/dependencies/ ./
COPY --from=builder /app/spring-boot-loader/ ./
COPY --from=builder /app/snapshot-dependencies/ ./
COPY --from=builder /app/application/ ./
EXPOSE 8080
ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]
Google Jib
Jib builds Docker images without a Dockerfile and without requiring Docker installed locally (for pushing to a registry).
| Feature | Jib | Classic Dockerfile |
|---|---|---|
| Dockerfile required | No | Yes |
| Docker installed | No (for registry push) | Yes |
| Automatic layers | Yes (deps / resources / code) | Manual |
| Reproducible build | Yes | Not by default |
Goals/Tasks:
| Maven Goal | Gradle Task | Description |
|---|---|---|
jib:build | jib | Build + push to registry (without Docker) |
jib:dockerBuild | jibDockerBuild | Build to local Docker |
jib:buildTar | jibBuildTar | Build to TAR file |
Maven pom.xml configuration:
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>3.4.0</version>
<configuration>
<from>
<image>eclipse-temurin:21-jre</image>
</from>
<to>
<image>registry.example.com/my-service:latest</image>
</to>
<container>
<ports>
<port>8080</port>
</ports>
</container>
</configuration>
</plugin>
5. Running Multi-container Apps with Docker Compose
Multi-container Architecture
A container should have a single responsibility. A typical web application requires at minimum:
graph TB
subgraph "Docker Compose Network: web-db-compose"
WA[web-app container\nSpring Boot WAR\nport 8080]
DB[(db container\nPostgreSQL\nport 5432)]
WA -->|JDBC| DB
end
Browser[Browser] -->|HTTP :8080| WA
HostFS[(Host Volume\n./db)] <-->|bind mount| DB
Docker Network Drivers
| Driver | Description | Use case |
|---|---|---|
bridge | Local private network (default) — isolation between networks | Local development, compose |
host | Shares host network namespace — no port mapping | Maximum performance |
overlay | Multi-engine connection (Swarm) | Kubernetes / Swarm |
ipvlan | Full IPv4/IPv6 control | Advanced networking |
macvlan | Assigned MAC address — appears as physical device | Physical network integration |
none | Disables all networking | Full isolation / custom driver |
docker-compose.yml — Web application + database
networks:
web-db-compose:
services:
web-app:
image: web-app-db
build:
context: .
dockerfile: web.Dockerfile
ports:
- "8080:8080"
networks:
- web-db-compose
depends_on:
db:
condition: service_healthy # Waits for healthcheck to pass
db:
image: postgres
environment:
- POSTGRES_PASSWORD=dbpassword
- POSTGRES_DB=bookdb
volumes:
- ./db:/var/lib/postgresql/data # Data persistence
networks:
- web-db-compose
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d bookdb"]
interval: 15s
retries: 5
start_period: 10s
timeout: 5s
docker-compose.yml — EFK Stack (Elasticsearch + Fluentd + Kibana)
services:
elasticsearch:
image: elasticsearch:7.17.22
environment:
- "discovery.type=single-node"
ports:
- "9200:9200"
healthcheck:
test: curl http://localhost:9200/_cat/health || exit 1
interval: 15s
retries: 5
start_period: 10s
timeout: 5s
kibana:
image: kibana:7.17.22
ports:
- "5601:5601"
fluentd:
image: my-fluentd
volumes:
- ./:/fluentd/etc/
ports:
- "24224:24224"
- "24224:24224/udp"
depends_on:
elasticsearch:
condition: service_healthy
environment:
- FLUENTD_CONF=fluent-efk.conf
api:
image: my-app-log
ports:
- "8080:8080"
depends_on:
fluentd:
condition: service_started
logging:
driver: fluentd
options:
fluentd-address: localhost:24224
tag: "api.{{.ImageName}}"
fluentd-async: "true"
Essential Docker Compose Commands
| Command | Description |
|---|---|
docker compose config | Validate and display configuration (merge of files) |
docker compose config -q | Validate silently |
docker compose build | Build images without starting |
docker compose up | Create and start all services |
docker compose up --build | Build images then start |
docker compose up -d | Start in detached mode (background) |
docker compose down | Stop and remove containers |
docker compose down -v | Stop and remove containers + volumes |
docker compose ps | List running services |
docker compose logs | Display logs for all services |
docker compose logs -f api | Follow logs for the api service |
docker compose restart api | Restart a specific service |
docker compose exec api bash | Open a shell in the api container |
docker compose stop | Stop without removing |
Note:
docker-compose(v1, standalone) has been deprecated since June 2023. Usedocker compose(v2, integrated CLI plugin).
Docker Compose Lifecycle
stateDiagram-v2
[*] --> Created: docker compose up
Created --> Starting: Resolving depends_on
Starting --> Healthy: healthcheck OK
Starting --> Running: service_started
Healthy --> Running: Service ready
Running --> Stopped: docker compose stop
Stopped --> Running: docker compose start
Running --> Removed: docker compose down
Removed --> [*]
state Running {
api --> db: JDBC
api --> fluentd: logs
fluentd --> elasticsearch: forward
kibana --> elasticsearch: query
}
6. Configuring Java Applications in Containers
Configuration Approaches
graph LR
A[Java Application\nin container]
B[ENV in Dockerfile\n→ default values] --> A
C[docker run -e VAR=val\n→ runtime override] --> A
D[--env-file .env\n→ variable file] --> A
E[Java System Properties\n-Dkey=value via ENTRYPOINT/CMD] --> A
F[Bind Mount\nexternal .properties file] --> A
G[Docker Compose override\n→ file per environment] --> A
Environment Variables — Dockerfile
FROM eclipse-temurin:21
WORKDIR /app
COPY target/service.jar app.jar
# Variables persistent in the container
ENV APP_VERSION=2.0 \
SPRING_PROFILES_ACTIVE=default
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
Environment Variables — docker run
# Override a variable
docker run -e SPRING_PROFILES_ACTIVE=dev -p 8080:8080 my-api
# Value from local environment (without =value)
docker run -e SPRING_PROFILES_ACTIVE -p 8080:8080 my-api
# Load from a file
docker run --env-file ./config/dev.env -p 8080:8080 my-api
Environment Variables — Docker Compose
services:
api:
image: api-service
environment:
# Dictionary
SPRING_PROFILES_ACTIVE: dev
APP_VERSION: "2.0"
DEBUG_MODE: "true" # Booleans must be quoted
# Or via file
env_file:
- ./config/dev.env
ENTRYPOINT + CMD for Java System Properties
Problem: Java system properties (-Dkey=val) must come before the JAR in the Java command, impossible to pass as arguments after the image.
Solution: Use an environment variable with the shell form of ENTRYPOINT:
FROM eclipse-temurin:21
WORKDIR /app
COPY target/service.jar app.jar
EXPOSE 8080
# Shell form: allows variable substitution
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar $APP_ARGS"]
# Runtime override with system properties
docker run -e JAVA_OPTS="-Dspring.profiles.active=prod -Xmx512m" \
-e APP_ARGS="--server.port=8081" \
-p 8081:8081 my-api
For Tomcat/WAR apps, via CATALINA_OPTS or JPDA_OPTS:
docker run -e CATALINA_OPTS="-Dspring.profiles.active=prod" -p 8080:8080 my-war-app
Mounting an External Properties File
Spring Boot allows loading properties files from the filesystem:
# Mount an external properties file in the container
docker run \
-e SPRING_CONFIG_ADDITIONAL_LOCATION=/config/ext_application.properties \
-v $(pwd)/config/ext_application.properties:/config/ext_application.properties:ro \
-p 8083:8083 \
my-api
Spring Boot properties for external files:
| Property | Description |
|---|---|
spring.config.location | Replaces the default config files |
spring.config.additional-location | Adds files (does not replace) |
spring.config.name | Change the file name (default: application) |
As an environment variable: replace
.with_→SPRING_CONFIG_ADDITIONAL_LOCATION
Docker Compose File Override per Environment
# Validate the merge of two compose files
docker compose -f docker-compose.yml -f docker-compose.dev.yml config
# Start with dev configuration
docker compose -f docker-compose.yml -f docker-compose.dev.yml up
docker-compose.yml (base):
services:
api:
image: api-service
docker-compose.dev.yml (dev override):
services:
api:
ports:
- "8081:8080"
environment:
SPRING_PROFILES_ACTIVE: dev
docker-compose.test.yml (test override):
services:
api:
ports:
- "8082:8080"
environment:
SPRING_PROFILES_ACTIVE: test
Merge behavior:
- Scalar options (e.g. env vars, labels) → replaced by the right-hand file
- Multivalue options (e.g.
ports,expose) → extended (union of both files)
7. Managing Application Logs with Docker
Docker Logging Model
graph LR
App[Java Application] -->|stdout / stderr| DockerDaemon[Docker Daemon]
DockerDaemon -->|json-file driver\ndefault| LogFile["/var/lib/docker/containers/\n*.log"]
DockerDaemon -->|fluentd driver| Fluentd[Fluentd]
DockerDaemon -->|awslogs driver| CloudWatch[AWS CloudWatch]
DockerDaemon -->|syslog driver| Syslog[Syslog]
Fluentd -->|concat plugin| MultilineHandler[Multiline log handling]
MultilineHandler --> Elasticsearch[Elasticsearch]
Elasticsearch --> Kibana[Kibana Dashboard]
LogFile -->|docker logs| DevConsole[Developer Console]
Docker Logging Drivers
| Driver | Description |
|---|---|
json-file | Default — local JSON files, no size limit by default |
local | Compressed binary format, automatic rotation |
syslog | Linux Syslog |
journald | systemd journal |
fluentd | Fluentd log collector |
awslogs | Amazon CloudWatch |
splunk | Splunk HTTP Event Collector |
gcplogs | Google Cloud Logging |
none | Disable all logs |
Log Inspection Commands
# Display all logs for a container
docker logs api
# Follow logs in real time
docker logs -f api
# Display the last 50 lines
docker logs --tail 50 api
# Logs since a timestamp
docker logs --since 2024-01-15T10:00:00 api
# Logs before a timestamp
docker logs --until 2024-01-15T12:00:00 api
# Docker Compose
docker compose logs
docker compose logs -f api
Multiline Log Problem (Stack Traces)
Docker records each line as a separate log event, which fragments Java stack traces:
2024-01-15T10:00:00.000Z ERROR c.e.ApiController - Error processing request
2024-01-15T10:00:00.001Z java.lang.NullPointerException: null
2024-01-15T10:00:00.002Z at com.example.ApiController.getBook(ApiController.java:45)
2024-01-15T10:00:00.003Z at com.example.ApiController.handle(ApiController.java:28)
Solutions:
| Approach | Description | Complexity |
|---|---|---|
| Log as JSON | Encode \n in a JSON field | Low |
Replace \n with \r | Carriage return character | Low |
| Fluentd + concat plugin | Group lines by regex | Medium |
| Filebeat + Logstash | Collect from a volume file | High |
| Log4j SocketAppender | Send directly via TCP | Medium |
Fluentd Configuration with concat plugin
fluent-ml.conf:
<source>
@type forward
port 24224
bind 0.0.0.0
</source>
<filter api.**>
@type concat
key log
# Pattern for the start of a Spring Boot 3 log entry
multiline_start_regexp /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z/
flush_interval 1s
</filter>
<match **>
@type stdout
</match>
fluentd.Dockerfile:
FROM fluent/fluentd:v1.16-debian-1
USER root
RUN gem install fluent-plugin-concat \
fluent-plugin-elasticsearch \
--no-document
USER fluent
8. IDE Integration — IntelliJ & VS Code
Plugin Comparison
| Feature | IntelliJ IDEA | Visual Studio Code |
|---|---|---|
| Image builds | ✅ Integrated UI | ✅ Palette commands |
| Container runs | ✅ | ✅ |
| Docker Compose support | ✅ | ✅ |
| Registry management | ✅ | ✅ |
| Run Maven/Gradle before image build | ✅ (Before Launch task) | ⚠️ Via tasks.json only |
| Command customization | ✅ UI Controls | ✅ tasks.json / templates |
| Paid extension required | Ultimate Edition | Free |
IntelliJ — Run/Debug Configuration
- Edit Configurations → ➕ → Dockerfile
- Configure: image tag, container name, port mappings
- Add Before Launch task →
Run Maven Goal: package - For debug: add Remote JVM Debug configuration (port 5005)
- Environment variable:
JPDA_OPTS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 - Override CMD:
catalina.sh jpda run
VS Code — Recommended Extensions
| Extension | Publisher | Role |
|---|---|---|
| Extension Pack for Java | Microsoft | Java, debugger, Maven, IntelliCode |
| Docker | Microsoft | Image/container management |
| Spring Boot Extension Pack | VMware | Spring Boot support |
VS Code — tasks.json for Docker
{
"version": "2.0.0",
"tasks": [
{
"label": "Maven Package",
"type": "shell",
"command": "mvn package -DskipTests",
"group": "build"
},
{
"label": "Docker Build Image",
"type": "docker-build",
"dockerBuild": {
"tag": "web-app-vscode:latest",
"dockerfile": "${workspaceFolder}/Dockerfile",
"context": "${workspaceFolder}"
},
"dependsOn": ["Maven Package"]
},
{
"label": "Run with Debug",
"type": "docker-run",
"dockerRun": {
"image": "web-app-vscode:latest",
"ports": [
{ "hostPort": 8080, "containerPort": 8080 },
{ "hostPort": 5005, "containerPort": 5005 }
],
"env": {
"CATALINA_OPTS": "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005"
},
"command": "catalina.sh jpda run"
}
}
]
}
9. Debugging Java Applications in Containers
JDWP — Java Debug Wire Protocol
Remote debugging relies on the JDWP agent (part of JPDA — Java Platform Debugging Architecture).
sequenceDiagram
participant IDE as IDE (IntelliJ / VS Code)
participant Container as Docker Container
participant JVM as JVM + JDWP Agent
Container->>JVM: Start with -agentlib:jdwp=...
JVM-->>Container: Listening on port 5005
IDE->>Container: TCP connection port 5005
IDE->>JVM: JDWP protocol (breakpoints, variables...)
JVM-->>IDE: Debug events (pause, values...)
IDE->>JVM: Step over / Step in / Continue
JDWP Argument
# General format
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
# Detailed options:
# transport=dt_socket → Communication via TCP socket
# server=y → JVM acts as server (debugger connects to it)
# suspend=n → Do not suspend JVM at startup
# address=*:5005 → Listen on all interfaces, port 5005
# (Since Java 9: the * is required for remote connections)
Debug Dockerfile — WAR Application (Tomcat)
FROM tomcat:11.0
ARG OUTDIR=build/libs
COPY ${OUTDIR}/web.war ${CATALINA_HOME}/webapps/ROOT.war
EXPOSE 8080
# CMD used (not ENTRYPOINT) to facilitate override at debug time
CMD ["catalina.sh", "run"]
# Start in debug mode
docker run \
-e CATALINA_OPTS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005" \
-p 8080:8080 \
-p 5005:5005 \
web-app-debug \
catalina.sh jpda run
docker-compose-debug.yml
services:
web-app:
image: web-app-vscode-debug
build:
context: .
args:
OUTDIR: target
dockerfile: Dockerfile
ports:
- "8080:8080"
- "5005:5005"
environment:
CATALINA_OPTS: >-
-agentlib:jdwp=transport=dt_socket,server=y,
suspend=n,address=*:5005
command: catalina.sh jpda run
Debug Dockerfile — Spring Boot JAR Application
FROM eclipse-temurin:21
WORKDIR /app
COPY target/service.jar app.jar
EXPOSE 8080
EXPOSE 5005
# Shell form for variable substitution
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
docker run \
-e JAVA_OPTS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005" \
-p 8080:8080 \
-p 5005:5005 \
my-api-debug
launch.json VS Code — Remote Debug
{
"version": "0.2.0",
"configurations": [
{
"type": "java",
"name": "Attach to Remote Program",
"request": "attach",
"hostName": "localhost",
"port": 5005
},
{
"type": "java",
"name": "Build and Attach to Remote Program",
"request": "attach",
"hostName": "localhost",
"port": 5005,
"preLaunchTask": "Run with Debug"
}
]
}
10. Quick Reference
Java Base Images — Comparison Table
| Image | Example tag | JDK/JRE | Approx. size | Notes |
|---|---|---|---|---|
eclipse-temurin | 21, 21-jre, 21-alpine | JDK/JRE | ~200–450 MB | ✅ Recommended, multi-arch |
eclipse-temurin | 21-alpine | JDK/JRE | ~100–180 MB | Lightweight, no Git/Bash |
amazoncorretto | 21, 21-alpine | JDK | ~250–400 MB | Amazon patches, LTS support |
bellsoft/liberica | 21-debian, 21-alpine | JDK/JRE | ~150–400 MB | Multiple variants |
maven | 3.9-eclipse-temurin-21 | JDK | ~400–600 MB | For build only |
gradle | 8.5-jdk21 | JDK | ~400–600 MB | For build only |
tomcat | 11.0, 10.1-jre21 | JRE | ~200–350 MB | Web server for WAR |
Essential Docker Commands
# === BUILD ===
docker build -t my-app:1.0 . # Build with default Dockerfile
docker build -f custom.Dockerfile -t my-app:1.0 . # Build with specific Dockerfile
docker build --build-arg OUTDIR=target -t my-app . # Build with ARG
docker buildx prune # Clear BuildKit cache
# === RUN ===
docker run -p 8080:8080 my-app # Map host:container port
docker run -d -p 8080:8080 my-app # Detached mode
docker run -e VAR=value my-app # Environment variable
docker run --env-file .env my-app # Env file
docker run -v $(pwd)/data:/app/data my-app # Bind mount
docker run --name my-container my-app # Name the container
docker run --rm my-app # Remove after exit
docker run --entrypoint /bin/sh my-app # Override entrypoint
# === MANAGEMENT ===
docker ps # Active containers
docker ps -a # All containers
docker images # Local images
docker stop my-container # Graceful stop
docker kill my-container # Forced stop
docker rm my-container # Remove container
docker rmi my-app:1.0 # Remove image
docker exec -it my-container bash # Shell in container
docker inspect my-container # Container details
docker stats # Real-time resource usage
# === LOGS ===
docker logs my-container # All logs
docker logs -f my-container # Follow logs
docker logs --tail 100 my-container # Last 100 lines
# === NETWORK ===
docker network create my-network # Create a network
docker network ls # List networks
docker network inspect my-network # Network details
docker run --network my-network my-app # Attach to network
# === REGISTRY ===
docker login registry.example.com # Authenticate
docker tag my-app:1.0 registry.example.com/my-app:1.0
docker push registry.example.com/my-app:1.0
docker pull registry.example.com/my-app:1.0
Build Approach Summary
graph TD
A[Need to build\na Java image] --> B{Preference?}
B --> C[Manual\nDockerfile]
B --> D[Maven or\nGradle plugin]
B --> E[Google Jib]
B --> F[Spring Boot\nBuildpacks]
C --> C1[Simple Dockerfile\n→ pre-compiled JAR/WAR]
C --> C2[Maven/Gradle image\n→ build inside container]
C --> C3[Multi-stage build\n→ build/runtime separation]
C --> C4[Layered Spring Boot\n→ cache optimization]
D --> D1[Fabric8 Docker\nMaven Plugin]
D --> D2[Gradle Docker\nPlugin Muschko]
E --> E1[Jib Maven Plugin]
E --> E2[Jib Gradle Plugin]
E1 --> E3[No Dockerfile\nNo local Docker]
F --> F1[mvn spring-boot:build-image]
F --> F2[gradle bootBuildImage]
F1 --> F3[OCI image\ncloud-native buildpacks]
Module Summary
| Module | Title | Key Topics |
|---|---|---|
| 1 | Course Overview | Introduction, objectives |
| 2 | Getting Started with Docker for Java | Docker concepts, base images, Hello World |
| 3 | Building with Dockerfiles | FROM, RUN, COPY, EXPOSE, ENTRYPOINT, CMD, multi-stage, BuildKit |
| 4 | Build Tools & Plugins | Fabric8, Gradle plugin, Spring Boot layers, Google Jib |
| 5 | Docker Compose | Networks, multi-container, healthcheck, depends_on, lifecycle |
| 6 | Configuration | ENV, —env-file, system properties, mount, compose override |
| 7 | Logging | Docker logging model, drivers, multiline problem, Fluentd, EFK stack |
| 8 | IDE Integration | IntelliJ plugin, VS Code plugin, tasks.json |
| 9 | Remote Debugging | JDWP, JPDA, launch.json, debug containers |
Training notes — Developing Java Apps with Docker (2023)
Search Terms
developing · java · apps · docker · containerization · containers · kubernetes · application · dockerfile · compose · debug · commands · environment · maven · plugin · applications · configuration · jar · variables · architecture · base · boot · cmd · comparison