Super Java Host
  1. Home
  2. Guides
  3. Java with Docker Compose

Run a Java application with Docker Compose on a VPS

A Dockerfile for a Java app, a Compose file with a database, correct memory limits for the JVM inside a container, and a simple update routine.

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 dbdata volume 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.

Your own Java server, billed monthly

Plans from €15.30 a month. Set up for you, or run for you.