Deploy Spring Boot applications with deploy4j
This guide demonstrates how to deploy a standard Spring Boot application using deploy4j.
We’ll cover the prerequisites, build and deployment steps, and testing procedures.
There is a sample deploy4j demo repository that can be used as a reference implementation.
We’ll be deploying a simple Spring Boot application that connects to a Postgres database.
graph TD
A[Spring Boot Application] --> B[Postgres Database]
Prerequisites
deploy4jinstalled (see installation guide)- Working Spring Boot application codebase that can be built into a Docker image.
- A built Docker image that can optionally be pushed to a Docker registry. Make a note of the image name and tag.
- A ready server with Digital Ocean, Hetzner, or similar provider. Ensure you have SSH access to the server. You can also test using a local droplet clone.
For this tutorial, we are using a Digital Ocean droplet with the following configuration:
- LON1 region
- Ubuntu 25.10 OS
- Basic type
- $6/mo CPU (1 vCPU, 1GB RAM, 25GB)
- SSH key authentication
This configuration can be applied using either the Digital Ocean web console or CLI tools.
doctl compute droplet create \
--image ubuntu-25-10-x64 \
--size s-1vcpu-1gb \
--region lon1 \
--vpc-uuid **** \
ubuntu-s-1vcpu-1gb-lon1-01
We get assigned 138.68.182.132 for our droplet. We can now SSH into the server to verify connectivity.
ssh root@138.68.182.132
Note: Doing this at least once will ensure that the server’s SSH fingerprint is added to your known hosts file.
Initialisation
Start by initialising the deploy4j configuration in your project. This will create a config/deploy.yml file and a .deploy4j/secrets in the root directory.
deploy4j init
The initialised config/deploy.yml can then be edited to suit your application requirements. We are going to configure the file for our Spring Boot application.
# The service name
service: deploy4j-demo
# The Docker image name
image: teggr/deploy4j-demo
# List of servers we want to deploy our application to
servers:
- 138.68.182.132
# Registry configuration for pulling the Docker image.
# We are using Docker Hub in this case.
# The username and password will be pulled from the .deploy4j/secrets file.
registry:
username:
- DOCKER_USERNAME
password:
- DOCKER_PASSWORD
# Environment variables for the Spring Boot application.
# We are configuring the database connection here.
# Datasource URL points to the Postgres accessory on the same host.
env:
clear:
SPRING_DATASOURCE_URL: jdbc:postgresql://138.68.182.132:5432/testdb
SPRING_DATASOURCE_USERNAME: testuser
SPRING_DATASOURCE_PASSWORD: testpass
# SSH configuration for connecting to the server.
# We are using key based authentication.
# The private key and passphrase will be pulled from the .deploy4j/secrets file.
ssh:
key_path:
- PRIVATE_KEY
key_passphrase:
- PRIVATE_KEY_PASSPHRASE
known_hosts_path:
- KNOWN_HOSTS_PATH
# Accessories configuration for the Postgres database.
accessories:
db:
image: postgres:18-alpine
host: 138.68.182.132
port: 5432
env:
clear:
POSTGRES_USER: testuser
POSTGRES_PASSWORD: testpass
POSTGRES_DB: testdb
directories:
- data:/var/lib/postgresql/18/docker
# Traefik configuration for load balancing and routing.
# We are enabling the dashboard for monitoring.
traefik:
publish: true
args:
api.dashboard: true
api.insecure: true
options:
publish: "8080:8080"
As noted in the configuration, we need to setup some secrets in the .deploy4j/secrets file. Create a .deploy4j/secrets file in the root of the project with the following contents:
DOCKER_PASSWORD=*****
DOCKER_USERNAME=*****
PRIVATE_KEY=<<path to your private key>>
PRIVATE_KEY_PASSPHRASE=*****
KNOWN_HOSTS_PATH=<<path to your known host file>>
We are now ready to deploy our Spring Boot application!
Deployment
The first time we deploy with deploy4j, we need to run the setup command. This will bootstrap the server, install Docker, setup Traefik, create the Postgres accessory, and deploy the application.
deploy4j setup --version 0.0.2-SNAPSHOT
Acquiring the deploy lock...
Ensure Docker is installed...
Missing Docker on 138.68.182.132. Installing...
Boot accessories...
Log into image registry...
Pull app image...
Ensure Traefik is running...
Detect stale containers...
Get most recent version available as an image...
Start container with version 0.0.2-SNAPSHOT using a nulls readiness delay (or reboot if already running)...
Container is healthy
First web container is healthy on 138.68.182.132, booting any other roles
Prune old containers and images...
Releasing the deploy lock...
=================================
Finished all in in 66 seconds
Now we can visit our Spring Boot application:
- Running application in the browser at
http://138.68.182.132/ - Traefik dashboard at
http://138.68.182.132:8080/dashboard/#/
architecture-beta
group api(cloud)[Droplet]
service db(database)[DB] in api
service disk1(disk)[Storage] in api
service traefik(server)[Traefik] in api
service app(server)[Application] in api
traefik:R -- L:app
app:R -- L:db
disk1:T -- B:db
Review
Now that the application is deployed, we can review the deployment using various deploy4j commands.
We can use deploy4j details to see the running containers on the server.
deploy4j details
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
60746d132cae traefik:v2.11 "/entrypoint.sh --pr…" 37 minutes ago Up 37 minutes 0.0.0.0:80->80/tcp, [::]:80->80/tcp, 0.0.0.0:8080->8080/tcp, [::]:8080->8080/tcp traefik
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7437bfeb9019 teggr/deploy4j-demo:0.0.2-SNAPSHOT "java -jar /app/app.…" 27 minutes ago Up 27 minutes 8080/tcp deploy4j-demo-web-0.0.2-SNAPSHOT
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9fcbbd2ac92e postgres:18-alpine "docker-entrypoint.s…" 37 minutes ago Up 37 minutes 0.0.0.0:5432->5432/tcp, [::]:5432->5432/tcp deploy4j-demo-db
We can view the app logs using deploy4j app logs.
deploy4j app logs
2025-11-08T10:02:16.456817906Z
2025-11-08T10:02:16.457096557Z . ____ _ __ _ _
2025-11-08T10:02:16.457105454Z /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
2025-11-08T10:02:16.457108142Z ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
2025-11-08T10:02:16.457110534Z \\/ ___)| |_)| | | | | || (_| | ) ) ) )
2025-11-08T10:02:16.457112659Z ' |____| .__|_| |_|_| |_\__, | / / / /
2025-11-08T10:02:16.457114813Z =========|_|==============|___/=/_/_/_/
2025-11-08T10:02:16.457117261Z
2025-11-08T10:02:16.459808902Z :: Spring Boot :: (v3.5.7)
2025-11-08T10:02:16.460015735Z
2025-11-08T10:02:16.746724702Z 2025-11-08T10:02:16.740Z INFO 1 --- [deploy4j-demo] [ main] d.d.jdemo.Deploy4jDemoApplication : Starting Deploy4jDemoApplication v0.0.2-SNAPSHOT using Java 24.0.2 with PID 1 (/app/app.jar started by root in /)
...
2025-11-08T10:02:28.450424932Z 2025-11-08T10:02:28.450Z INFO 1 --- [deploy4j-demo] [ main] liquibase.lockservice : Successfully released change log lock
2025-11-08T10:02:28.455829735Z 2025-11-08T10:02:28.454Z INFO 1 --- [deploy4j-demo] [ main] liquibase.command : Command execution complete
2025-11-08T10:02:32.433790133Z 2025-11-08T10:02:32.432Z INFO 1 --- [deploy4j-demo] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path '/'
2025-11-08T10:02:32.513261762Z 2025-11-08T10:02:32.511Z INFO 1 --- [deploy4j-demo] [ main] d.d.jdemo.Deploy4jDemoApplication : Started Deploy4jDemoApplication in 17.921 seconds (process running for 20.742)
2025-11-08T10:03:00.721365167Z 2025-11-08T10:03:00.720Z INFO 1 --- [deploy4j-demo] [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2025-11-08T10:03:00.723828625Z 2025-11-08T10:03:00.721Z INFO 1 --- [deploy4j-demo] [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2025-11-08T10:03:00.726937918Z 2025-11-08T10:03:00.726Z INFO 1 --- [deploy4j-demo] [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 2 ms
Deploy4j also maintains an audit log of all deployments. We can view this using deploy4j audit.
deploy4j audit
[2025-11-08T09:51:49.2596283Z] Pushed env files
[2025-11-08T09:51:51.4973715Z] Booted db accessory
[2025-11-08T09:52:00.9176809Z] Pulled image with version 0.0.2-SNAPSHOT
[2025-11-08T09:52:16.7819702Z] Booted app version 0.0.2-SNAPSHOT
[2025-11-08T09:52:17.8790963Z] Tagging teggr/deploy4j-demo:0.0.2-SNAPSHOT as the latest image
[2025-11-08T09:52:18.2035488Z] Pruned containers
[2025-11-08T09:52:18.6692753Z] Pruned images