Intermediate

Developing Java Apps with Docker

Dockerize Java apps with build tools, Compose, container config, logging and IDE debugging.

Table of Contents

  1. Course Overview
  2. Getting Started with Docker for Java
  3. Building Java Applications with Dockerfiles
  4. Building with Build Tools and Plugins
  5. Running Multi-container Apps with Docker Compose
  6. Configuring Java Applications in Containers
  7. Managing Application Logs with Docker
  8. IDE Integration — IntelliJ & VS Code
  9. Debugging Java Applications in Containers
  10. 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 ConceptJava Analogy
ImageClass
ContainerObject (instance)
DockerfileClass source code
Docker HubMaven Central / Nexus
LayerFilesystem 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.

ImagePublisherNotable featureRecommendation
eclipse-temurinEclipse AdoptiumTCK-certified OpenJDK, multi-architecture✅ Recommended
amazoncorrettoAmazonSecurity + perf patches, free LTS support✅ Good option
bellsoft/libericaBellSoftDebian / Alpine variants✅ Viable
openjdkOpenJDKDeprecated — do not use❌ Deprecated
javaDocker LibraryDeprecated — do not use❌ Deprecated
Oracle JDKOracleRestrictive 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

InstructionFormBehaviorOverride with docker run
ENTRYPOINTexec / shellMain container command--entrypoint flag
CMDexec / shellDefault arguments or default commandArguments after the image

Usage rules:

  1. Define at least CMD or ENTRYPOINT
  2. ENTRYPOINT for a container used as an executable
  3. CMD for default arguments to an ENTRYPOINT
  4. CMD is easily overridable — prefer CMD when 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 GoalDefault phaseDescription
docker:buildpackageBuilds the Docker image
docker:start / docker:runpre-integration-testCreates and starts the container
docker:stoppost-integration-testStops the container
docker:pushdeployPushes 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:

PluginUse case
Remote API PluginInteraction with the remote Docker API
Java Application PluginBuild and push images for standard Java apps
Spring Boot Application PluginBuild and push images for Spring Boot apps

Java Application Plugin tasks:

TaskDescription
dockerSyncBuildContextCopies files to the temp directory
dockerCreateDockerfileGenerates the Dockerfile automatically
dockerBuildImageBuilds the image
dockerPushImagePushes 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).

FeatureJibClassic Dockerfile
Dockerfile requiredNoYes
Docker installedNo (for registry push)Yes
Automatic layersYes (deps / resources / code)Manual
Reproducible buildYesNot by default

Goals/Tasks:

Maven GoalGradle TaskDescription
jib:buildjibBuild + push to registry (without Docker)
jib:dockerBuildjibDockerBuildBuild to local Docker
jib:buildTarjibBuildTarBuild 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

DriverDescriptionUse case
bridgeLocal private network (default) — isolation between networksLocal development, compose
hostShares host network namespace — no port mappingMaximum performance
overlayMulti-engine connection (Swarm)Kubernetes / Swarm
ipvlanFull IPv4/IPv6 controlAdvanced networking
macvlanAssigned MAC address — appears as physical devicePhysical network integration
noneDisables all networkingFull 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

CommandDescription
docker compose configValidate and display configuration (merge of files)
docker compose config -qValidate silently
docker compose buildBuild images without starting
docker compose upCreate and start all services
docker compose up --buildBuild images then start
docker compose up -dStart in detached mode (background)
docker compose downStop and remove containers
docker compose down -vStop and remove containers + volumes
docker compose psList running services
docker compose logsDisplay logs for all services
docker compose logs -f apiFollow logs for the api service
docker compose restart apiRestart a specific service
docker compose exec api bashOpen a shell in the api container
docker compose stopStop without removing

Note: docker-compose (v1, standalone) has been deprecated since June 2023. Use docker 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:

PropertyDescription
spring.config.locationReplaces the default config files
spring.config.additional-locationAdds files (does not replace)
spring.config.nameChange 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

DriverDescription
json-fileDefault — local JSON files, no size limit by default
localCompressed binary format, automatic rotation
syslogLinux Syslog
journaldsystemd journal
fluentdFluentd log collector
awslogsAmazon CloudWatch
splunkSplunk HTTP Event Collector
gcplogsGoogle Cloud Logging
noneDisable 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:

ApproachDescriptionComplexity
Log as JSONEncode \n in a JSON fieldLow
Replace \n with \rCarriage return characterLow
Fluentd + concat pluginGroup lines by regexMedium
Filebeat + LogstashCollect from a volume fileHigh
Log4j SocketAppenderSend directly via TCPMedium

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

FeatureIntelliJ IDEAVisual 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 requiredUltimate EditionFree

IntelliJ — Run/Debug Configuration

  1. Edit Configurations → ➕ → Dockerfile
  2. Configure: image tag, container name, port mappings
  3. Add Before Launch task → Run Maven Goal: package
  4. For debug: add Remote JVM Debug configuration (port 5005)
  5. Environment variable: JPDA_OPTS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
  6. Override CMD: catalina.sh jpda run
ExtensionPublisherRole
Extension Pack for JavaMicrosoftJava, debugger, Maven, IntelliCode
DockerMicrosoftImage/container management
Spring Boot Extension PackVMwareSpring 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

ImageExample tagJDK/JREApprox. sizeNotes
eclipse-temurin21, 21-jre, 21-alpineJDK/JRE~200–450 MB✅ Recommended, multi-arch
eclipse-temurin21-alpineJDK/JRE~100–180 MBLightweight, no Git/Bash
amazoncorretto21, 21-alpineJDK~250–400 MBAmazon patches, LTS support
bellsoft/liberica21-debian, 21-alpineJDK/JRE~150–400 MBMultiple variants
maven3.9-eclipse-temurin-21JDK~400–600 MBFor build only
gradle8.5-jdk21JDK~400–600 MBFor build only
tomcat11.0, 10.1-jre21JRE~200–350 MBWeb 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

ModuleTitleKey Topics
1Course OverviewIntroduction, objectives
2Getting Started with Docker for JavaDocker concepts, base images, Hello World
3Building with DockerfilesFROM, RUN, COPY, EXPOSE, ENTRYPOINT, CMD, multi-stage, BuildKit
4Build Tools & PluginsFabric8, Gradle plugin, Spring Boot layers, Google Jib
5Docker ComposeNetworks, multi-container, healthcheck, depends_on, lifecycle
6ConfigurationENV, —env-file, system properties, mount, compose override
7LoggingDocker logging model, drivers, multiline problem, Fluentd, EFK stack
8IDE IntegrationIntelliJ plugin, VS Code plugin, tasks.json
9Remote DebuggingJDWP, 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

Interested in this course?

Contact us to book it or get a custom training plan for your team.