Docker for Java Developers: Containerize a Spring Boot Application

JAVA DEVELOPMENT · CONTAINERS AND DEPLOYMENT

Docker for Java Developers: Containerize a Spring Boot Application

Understand images, containers, networking, and storage—and package your Spring Boot application with a predictable runtime.

Your Spring Boot application starts on your laptop but fails on another machine. The Java version differs, a configuration file is missing, or the startup command has changed.

Docker helps reduce these environment differences by packaging an application with its runtime and required files. The resulting image becomes a consistent deployment artifact.

This guide explains Docker’s core concepts, walks through a Java 17 Spring Boot example, and covers networking, persistent storage, troubleshooting, and common interview questions.

1. What is Docker?

Docker provides tools to build, distribute, and run containerized applications. A container runs an application process with an isolated filesystem view and configurable networking and resource constraints.

Linux containers share the kernel of their Linux host. When running Linux containers through Docker Desktop on Windows or macOS, that Linux environment is provided through virtualization.

Understand the essential terms

Term Meaning Example
Dockerfile Instructions for building an image. Choose Java, copy a JAR, define startup.
Image A packaged template used to create containers. spring-demo:1.0
Container An instance of an image, with its own runtime state. A running Spring Boot process.
Registry A service that stores and distributes images. Docker Hub or Amazon ECR.
Volume Docker-managed storage with a separate lifecycle. Persistent database files.

Remember: Building an image does not start your application. Running a container creates an instance and executes its configured startup command.

2. Why Docker helps Spring Boot developers

An executable Spring Boot JAR includes application dependencies, but it still needs a compatible Java runtime. A container image packages that runtime alongside the JAR.

  • Consistency: Deploy the same application image across environments.
  • Isolation: Run services with separate filesystems and configurations.
  • Repeatability: Describe packaging in a version-controlled Dockerfile.
  • Local development: Run supporting services without installing each directly.

Docker does not remove every environment difference. CPU architecture, external services, mounted files, credentials, and resource limits still affect application behavior.

3. Package a Spring Boot application step by step

Step 1: Prepare an executable JAR

Start with a Maven-based Spring Boot web application that targets Java 17, listens on port 8080, and already runs successfully. Ensure the Spring Boot Maven plugin produces an executable JAR.

Set the output name inside your existing Maven <build> section:

<finalName>app</finalName>

Build the application from the project root:

# Compile, run tests, and package the application.
./mvnw clean package

On Windows Command Prompt, use mvnw.cmd clean package. The Dockerfile below expects target/app.jar.

Step 2: Create the Dockerfile

Create a file named Dockerfile beside pom.xml:

# Use a Java 17 runtime from Eclipse Temurin.
FROM eclipse-temurin:17-jre-jammy

# Set the application's working directory.
WORKDIR /app

# Create an unprivileged account for the application.
RUN groupadd --system app && useradd --system --gid app app

# Copy the executable JAR and assign ownership.
COPY --chown=app:app target/app.jar app.jar

# Run the application without root privileges.
USER app

# Document the port used by the application.
EXPOSE 8080

# Launch Java directly using exec-form syntax.
ENTRYPOINT ["java", "-jar", "/app/app.jar"]

What these instructions achieve

FROM selects the runtime image. COPY adds the packaged application. USER changes the runtime account. ENTRYPOINT defines the default executable.

EXPOSE documents the intended container port; it does not publish that port on your computer. Port publishing happens when the container is created.

Step 3: Limit the build context

Create .dockerignore beside the Dockerfile. This example sends only the packaged JAR and required build files:

**
!Dockerfile
!.dockerignore
!target/
!target/app.jar

The pattern deliberately keeps target/app.jar. Excluding the entire target directory without restoring that file would break COPY.

Step 4: Build and run

With Docker running, execute these commands from the project root:

# Build the image using the current directory as context.
docker build -t spring-demo:1.0 .

# Create a background container and publish its application port locally.
docker run -d --name spring-demo -p 127.0.0.1:8080:8080 spring-demo:1.0

# Follow the application startup logs.
docker logs -f spring-demo

Open a known application endpoint through http://localhost:8080. A 404 at the root path can simply mean your application has no controller mapped to /.

Port mapping: 127.0.0.1:8080:8080 binds host port 8080 on the loopback interface to container port 8080. Use host port 8081 instead if 8080 is already occupied.

4. Configure services with Docker Compose

Docker Compose describes application services in a YAML file. For the same Spring Boot application, create compose.yaml:

services:
  app:
    build: .
    image: spring-demo:1.0
    ports:
      - "127.0.0.1:8081:8080"
    environment:
      SERVER_PORT: "8080"

This uses host port 8081, so it can coexist with the earlier container on port 8080.

# Build and start the Compose service.
docker compose up -d --build

# View logs for the app service.
docker compose logs -f app

# Stop and remove this Compose application's containers and network.
docker compose down

Why localhost often causes database failures

Inside an ordinary container, localhost refers to that container, not another service. If a PostgreSQL service is named db in the same Compose project, use jdbc:postgresql://db:5432/appdb.

Compose provides service-name discovery on its default network. Service-to-service connections use container ports, not host port mappings.

Basic depends_on controls startup order, not database readiness. Use a database health check with condition: service_healthy when appropriate. Applications still need to handle later connection failures.

5. Keep persistent data outside the container layer

A container has a writable layer. Its files remain when that same container stops and restarts, but disappear when the container is removed.

Use a named volume for data that must survive container replacement. For databases, mount it at the data directory documented for the specific database image and version.

A bind mount instead connects a particular host path to a container. It is useful for local source files or configuration, but depends on the host’s directory structure.

Persistence is not backup: A volume can outlive a container, but accidental deletion or host failure can still destroy data. Maintain a separate backup and restore process.

6. Improve images before deployment

Separate building from running

Our example builds the JAR before building the image. A multi-stage Dockerfile can instead compile and test in a JDK build stage, then copy only the resulting artifact into a runtime stage.

This keeps compilers and build tools out of the final image. Spring Boot also supports creating images with Cloud Native Buildpacks as an alternative to maintaining a Dockerfile.

Make releases traceable

  • Use identifiable application tags, such as a release number or commit ID.
  • Remember that tags can move; digests identify specific image content.
  • Refresh base images and scan dependencies through your delivery pipeline.
  • Supply secrets at runtime through an appropriate secret-management mechanism.
  • Set resource limits and leave memory headroom beyond the Java heap.

7. Troubleshoot common Docker problems

Symptom What to check
Container exits immediately Use docker ps -a and docker logs to inspect startup failure.
JAR not found during build Verify packaging, filename, build context, and .dockerignore.
Application cannot be reached Check published ports and the application's listening address.
Database connection is refused Check service name, network, credentials, and readiness.
Old behavior remains after editing code Rebuild the JAR and image, then recreate the container.

8. Docker interview questions

How is a container different from a virtual machine?

A virtual machine has its own guest kernel. Containers share their host kernel while isolating application processes and resources. Containers are not complete virtual machines.

Does restarting a container load a newly built image?

No. Restarting uses the existing container. Create a replacement container from the new image to deploy the new application.

Does Docker automatically scale an application?

No. Docker packages and runs containers. Scaling requires additional configuration, capacity, traffic distribution, and often an orchestration platform.

9. Your next practical step

Containerize one working Spring Boot service. Verify its endpoints, inspect its logs, change a runtime setting, and rebuild it after a code change.

Then add a database through Compose and test persistence across container replacement. These exercises build the deployment skills behind confident interview answers.

Official references

Comments

Popular posts from this blog

Beyond Standard RAG: The Architecture of Agentic Retrieval and Self-Correction Loops

The Architecture of Vision-Language Models: How Multimodal AI Sees and Understands the World

Java Collection Framework: A Practical Guide with Examples