# Spring Boot Dockerfile Generator

URL: /dockerizer/spring-boot

Generate a production Dockerfile for a Spring Boot service using layered jar extraction so dependency layers stay cached between builds.

## Default configuration

- `appName` (App name): spring-app — Used for the image tag, the compose service and the OCI labels.

- `port` (Port): 8080 — The port the app listens on inside the container. Keep it above 1024 so the process can bind it without root.

- `buildTool` (Build tool): maven

- `jdkVersion` (JDK version): 21

- `layered` (Layered jar extraction): true — Splits the fat jar so dependencies and application code become separate layers. Requires Spring Boot 3.3 or later.

- `database` (Database service): none — Adds the database to docker-compose.yml with a healthcheck, a named volume and a DATABASE_URL wired into the app.

- `redis` (Redis service): false — Adds Redis to docker-compose.yml and exposes REDIS_URL to the app.

- `cacheMounts` (BuildKit cache mounts): true — Persists the package manager store between builds. Repeat builds skip the download entirely.

- `multiArch` (Multi-architecture build): false — Adds BUILDPLATFORM/TARGETARCH so `docker buildx build --platform linux/amd64,linux/arm64` cross-compiles natively.

- `healthcheck` (Healthcheck): true — Adds a HEALTHCHECK so orchestrators can restart an unresponsive container.

- `ociLabels` (OCI labels): false — Adds org.opencontainers.image.* metadata to the final image.

## Generated files

### Dockerfile

Multi-stage build: dependencies, compilation and the runtime image are separate, so only what the app needs at runtime ships.

```docker
# syntax=docker/dockerfile:1
# Generated by Easypanel Dockerizer — https://easypanel.io/dockerizer

# --- Build --------------------------------------------
FROM eclipse-temurin:21-jdk-alpine AS build
WORKDIR /workspace

# Wrapper and manifest first, so source edits keep the dependency cache
COPY mvnw pom.xml ./
COPY .mvn .mvn
RUN --mount=type=cache,id=maven-repo,target=/root/.m2,sharing=locked \
    ./mvnw dependency:go-offline -B
COPY src src
RUN --mount=type=cache,id=maven-repo,target=/root/.m2,sharing=locked \
    ./mvnw package -DskipTests -B
RUN cp target/*.jar app.jar

# Split the fat jar so dependencies and application code land in separate layers
RUN java -Djarmode=tools -jar app.jar extract --layers --launcher --destination extracted

# --- Runtime ------------------------------------------
FROM eclipse-temurin:21-jre-alpine AS runtime
WORKDIR /app
RUN addgroup --system --gid 1001 spring \
 && adduser --system --uid 1001 --ingroup spring spring

# Ordered least- to most-frequently changed, so caching works
COPY --from=build --chown=spring:spring /workspace/extracted/dependencies/ ./
COPY --from=build --chown=spring:spring /workspace/extracted/spring-boot-loader/ ./
COPY --from=build --chown=spring:spring /workspace/extracted/snapshot-dependencies/ ./
COPY --from=build --chown=spring:spring /workspace/extracted/application/ ./
USER spring
ENV SERVER_PORT=8080
# Respect the container's memory limit instead of the host's
ENV JAVA_OPTS="-XX:MaxRAMPercentage=75.0 -XX:+UseContainerSupport"
EXPOSE 8080
STOPSIGNAL SIGTERM

# Lets Docker, Compose and Easypanel see when the app is wedged
HEALTHCHECK --interval=30s --timeout=5s --start-period=40s --retries=3 \
  CMD ["wget", "-q", "--spider", "http://127.0.0.1:8080/actuator/health"]
ENTRYPOINT ["sh", "-c", "exec java $JAVA_OPTS org.springframework.boot.loader.launch.JarLauncher"]

```

### .dockerignore

Keeps the build context small and stops secrets and local dependencies from reaching an image layer.

```bash
# Version control
.git
.gitignore
.github

# Secrets — never bake these into an image layer
.env
.env.*
!.env.example
*.pem
*.key

# Editor and OS noise
.vscode
.idea
.DS_Store
Thumbs.db

# Docs and local tooling
README.md
LICENSE
docs
.editorconfig
docker-compose*.yml
Dockerfile*
.dockerignore

# Build output — recompiled inside the image
target
build
.gradle
*.class
*.jar
!gradle/wrapper/gradle-wrapper.jar

```

### docker-compose.yml

Runs the image locally with its backing services, wired together and health-gated.

```yaml
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    restart: unless-stopped
    ports:
      - "8080:8080"

```

## Frequently asked questions

### Why extract the jar instead of just running it?

A fat jar is a single 60 MB layer, so changing one class re-pushes all of it. Extracted layers separate your dependencies from your code, and dependencies change far less often.

### Why is the entrypoint wrapped in sh -c?

So $JAVA_OPTS is expanded. `exec` replaces the shell with the JVM afterwards, so Java still runs as PID 1 and receives SIGTERM directly.

## Related generators

- [.NET](/dockerizer/dotnet)

- [Go](/dockerizer/golang)

- [Ruby on Rails](/dockerizer/rails)

- [Django](/dockerizer/django)