1. Install Docker
Use Docker's official repository for current versions. The steps are in Docker's documentation for Ubuntu; in short:
sudo apt update
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | sudo tee /etc/apt/sources.list.d/docker.list
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo docker run --rm hello-world
2. A Dockerfile for the application
A JRE-only base image keeps the image small. This one runs a Spring Boot or Quarkus uber-jar:
FROM eclipse-temurin:21-jre
RUN useradd --system --no-create-home app
WORKDIR /app
COPY target/app.jar /app/app.jar
USER app
EXPOSE 8080
ENTRYPOINT ["java", "-XX:MaxRAMPercentage=70", "-XX:+ExitOnOutOfMemoryError", "-jar", "/app/app.jar"]
Since Java 10 the JVM reads the container memory limit. -XX:MaxRAMPercentage=70 sets the heap to 70% of that limit and leaves room for metaspace and thread stacks. Do not leave the default of 25%, which wastes memory, and do not go above 75%.
3. Compose file with a database
services:
app:
image: registry.example.com/myapp:latest # or build: .
restart: unless-stopped
ports:
- "127.0.0.1:8080:8080"
environment:
SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/mydb
SPRING_DATASOURCE_USERNAME: myuser
SPRING_DATASOURCE_PASSWORD: ${DB_PASSWORD}
depends_on:
db:
condition: service_healthy
mem_limit: 1g
db:
image: postgres:17
restart: unless-stopped
environment:
POSTGRES_DB: mydb
POSTGRES_USER: myuser
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- dbdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U myuser -d mydb"]
interval: 5s
timeout: 3s
retries: 10
mem_limit: 512m
volumes:
dbdata:
Two details matter. The port is published on 127.0.0.1 only: Docker writes its own firewall rules and a plain "8080:8080" bypasses UFW and exposes the port to the internet. And the password lives in a .env file next to the Compose file, with permissions 600:
echo "DB_PASSWORD=$(openssl rand -hex 16)" > .env && chmod 600 .env
docker compose up -d
docker compose ps
docker compose logs -f app
4. Nginx and SSL
Proxy to 127.0.0.1:8080 exactly as in the Nginx guide.
5. Update the application
docker compose pull app # if you use a registry
docker compose up -d app
docker image prune -f
Compose recreates only the container whose image changed. The database stays up.
Things to watch
- Set a log size limit, or logs fill the disk: add
logging: {driver: json-file, options: {max-size: "10m", max-file: "3"}}to each service. - Back up the
dbdatavolume with a dump, not by copying files of a running database. See backups with restic. - Docker adds memory overhead. On a 2 GB server a Compose stack with a database is tight: choose 4 GB.
Our Docker hosting for Java plans come with Docker and Compose installed and a deploy command that pulls your image.
Last updated October 29, 2026.