Deploy Spring Boot Applications

In simple terms: Deployment means taking an application from a developer's computer and running it reliably where real users can access it. A good deployment is repeatable, configurable, observable, secure, and easy to roll back.
Typical deployment path
BuildCompile and test
PackageCreate JAR or image
ConfigureProfiles and secrets
RunServer or container
MonitorHealth and logs

Deployment options

Option Best for What you provide
Executable JAR VM, bare server, or simple hosting Java runtime and configuration
Docker Consistent local, CI, and production environments Image and environment variables
Docker Compose Local development with application dependencies Compose file and volumes
Kubernetes Multiple replicas and rolling deployments Container image and manifests

Build and run an executable JAR

Spring Boot packages the application, embedded server, and required libraries into one executable JAR. The target machine needs a compatible Java runtime, but it does not need a separately installed Tomcat.

./mvnw clean test
./mvnw clean package

java -jar target/employee-service-1.0.0.jar

# Override settings for one run
java -jar target/employee-service-1.0.0.jar \
  --server.port=9090 \
  --spring.profiles.active=prod

Configure each environment

Keep the same artifact across development, testing, and production. Change environment-specific values with profiles, environment variables, or a secret manager instead of rebuilding the JAR.

# application-prod.properties
server.port=8080
spring.datasource.url=DATABASE_URL
spring.datasource.username=DATABASE_USERNAME
spring.datasource.password=DATABASE_PASSWORD
spring.jpa.hibernate.ddl-auto=validate
management.endpoints.web.exposure.include=health,info,prometheus

# Runtime environment variables
SPRING_PROFILES_ACTIVE=prod
DATABASE_URL=jdbc:postgresql://db:5432/employee_portal
  • Never commit database passwords, API keys, or JWT secrets.
  • Use ddl-auto=validate with Flyway or Liquibase in production.
  • Use a separate database and credentials for every environment.

Dockerize the application

Docker puts the Java runtime and application into an image. The same image can run on a developer laptop, in CI, or in a cloud platform.

Dockerfile

FROM eclipse-temurin:21-jre
WORKDIR /app
COPY target/employee-service-1.0.0.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

Build and run the image

./mvnw clean package
docker build -t javacodeex/employee-service:1.0.0 .

docker run --name employee-service \
  -p 8080:8080 \
  -e SPRING_PROFILES_ACTIVE=prod \
  -e DATABASE_URL=jdbc:postgresql://host.docker.internal:5432/employee_portal \
  -e DATABASE_USERNAME=postgres \
  -e DATABASE_PASSWORD=change-me \
  javacodeex/employee-service:1.0.0

-p 8080:8080 maps the host port to the container port. Environment variables are injected at runtime, so the image does not contain a database password.

Multi-stage Docker build

A multi-stage build compiles the application in one stage and copies only the final JAR into a smaller runtime image.

FROM maven:3.9-eclipse-temurin-21 AS build
WORKDIR /workspace
COPY pom.xml .
COPY src ./src
RUN mvn clean package -DskipTests

FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=build /workspace/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

Run Spring Boot and PostgreSQL with Docker Compose

Compose is useful for local development because it starts the application and database together on one private network.

services:
  db:
    image: postgres:16
    environment:
      POSTGRES_DB: employee_portal
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: local-password
    ports:
      - "5432:5432"
    volumes:
      - postgres-data:/var/lib/postgresql/data

  app:
    build: .
    depends_on:
      - db
    ports:
      - "8080:8080"
    environment:
      SPRING_PROFILES_ACTIVE: docker
      DATABASE_URL: jdbc:postgresql://db:5432/employee_portal
      DATABASE_USERNAME: postgres
      DATABASE_PASSWORD: local-password

volumes:
  postgres-data:
docker compose up --build
docker compose logs -f app
docker compose down

Inside Compose, the application connects to host db, not localhost. A Compose service name becomes a network hostname.

Health checks and readiness

Add Actuator so a platform can decide whether the application is alive and ready. Liveness controls restarts; readiness controls traffic.

management.endpoint.health.probes.enabled=true
management.endpoints.web.exposure.include=health,info

GET /actuator/health/liveness
GET /actuator/health/readiness
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s \
  CMD wget --no-verbose --tries=1 \
  --spider http://localhost:8080/actuator/health/readiness || exit 1

Deploy the container to Kubernetes

Kubernetes can run multiple copies, send traffic through a Service, and use probes to control traffic and restarts.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: employee-service
spec:
  replicas: 2
  selector:
    matchLabels:
      app: employee-service
  template:
    metadata:
      labels:
        app: employee-service
    spec:
      containers:
        - name: employee-service
          image: javacodeex/employee-service:1.0.0
          ports:
            - containerPort: 8080
          env:
            - name: SPRING_PROFILES_ACTIVE
              value: prod
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: 8080
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8080
          resources:
            requests:
              memory: "512Mi"
              cpu: "250m"
            limits:
              memory: "1Gi"
              cpu: "1000m"
---
apiVersion: v1
kind: Service
metadata:
  name: employee-service
spec:
  selector:
    app: employee-service
  ports:
    - port: 80
      targetPort: 8080
kubectl apply -f employee-service.yaml
kubectl get pods
kubectl logs -f deployment/employee-service
kubectl rollout status deployment/employee-service

Logging and monitoring

Containers should write logs to standard output so Docker, Kubernetes, or a cloud logging platform can collect them. Monitor latency, error rate, traffic, CPU, memory, and database connection usage.

logging.level.root=INFO
logging.level.com.javacodeex=INFO
logging.pattern.console=%d{ISO8601} %-5level [%X{requestId}] %logger - %msg%n

GET /actuator/health/readiness
GET /actuator/metrics/http.server.requests
GET /actuator/prometheus

CI/CD deployment flow

  1. Push code to the source repository.
  2. Run unit tests, integration tests, and security checks.
  3. Build the JAR and Docker image.
  4. Tag the image with a release or commit ID.
  5. Push the image to a private container registry.
  6. Deploy the image and wait for readiness checks.
  7. Stop or roll back automatically when health checks fail.
javacodeex/employee-service:1.0.0
javacodeex/employee-service:git-a1b2c3d

Deployment security

  • Use HTTPS at the load balancer or ingress.
  • Store secrets in a secret manager or Kubernetes Secret.
  • Run containers as a non-root user where possible.
  • Use a small JRE image and scan images for vulnerabilities.
  • Expose only required Actuator endpoints.
  • Restrict database access to the application network.
  • Keep dependencies and base images patched.

Rollback and troubleshooting

Keep the previous image available and make database migrations backward compatible when possible. A deployment is incomplete until recovery has been tested.

kubectl rollout history deployment/employee-service
kubectl rollout undo deployment/employee-service
kubectl describe pod <pod-name>
kubectl logs <pod-name>
Symptom First checks
Container exits Application logs, Java version, required environment variables
Readiness fails Database URL, credentials, migrations, dependency health
502 or 503 response Service port, target port, readiness probe, network rules
Application is slow HTTP latency, CPU, memory, and database metrics

Production checklist

  • Run tests before packaging and scan dependencies and images.
  • Use an immutable, versioned JAR or Docker image.
  • Keep environment configuration and secrets outside the image.
  • Use database migrations and take backups before risky changes.
  • Configure readiness and liveness probes.
  • Set memory and CPU requests and limits for containers.
  • Collect structured logs, metrics, and alerts.
  • Document rollback steps and test them.
  • Use HTTPS, least privilege, and restricted Actuator endpoints.

Spring Boot Deployment FAQs

Should configuration be packaged inside the JAR?

Package safe defaults only. Keep environment-specific URLs, credentials, feature flags, and secrets outside the artifact.

Should Spring Boot applications run in Docker?

Docker is useful when you need repeatable environments and consistent promotion from development to production. Use a small runtime image and scan it regularly.

What should a CI/CD pipeline verify?

Build and unit tests, integration tests, dependency and image scans, configuration checks, health verification, deployment status, and rollback readiness.

Back to Spring Boot Course

Continue learning