Docker is a platform for packaging, deploying, and running applications in lightweight, portable containers that are isolated from the underlying hardware and operating system. If you have a Java application you want to containerize, this guide walks you through every step: installing Docker, building a Java-capable image, deploying a Java application, and persisting data across container restarts.
Note: This tutorial was originally written for an early version of Docker on Ubuntu 12.10 and uses the legacy lxc-docker package and docker commit workflow. The core concepts still apply, but modern Docker installations should use the official Docker Engine packages from docs.docker.com and prefer Dockerfiles over interactive commits. See the review notes at the bottom for details.
Prerequisites
Docker runs on Linux, macOS, Windows, Windows Server, and major cloud platforms including AWS and Azure. The examples below use Ubuntu Linux. The demonstration application is Bitbucket Server (Atlassian’s self-hosted Git repository manager, previously called Stash), but you can substitute any Java application.
Before you start, install a few common utilities on your host machine:
sudo apt-get install vim curl gitCode language: JavaScript (javascript)
Step 1: Install Docker
The following commands show the historical installation path used in the original tutorial (Ubuntu 12.10, lxc-docker package). For current Ubuntu versions, follow the official Docker installation guide instead.
Install kernel extras required by Docker:
sudo apt-get install linux-image-extra-$(uname -r)Code language: JavaScript (javascript)
Install software-properties-common to gain access to add-apt-repository:
sudo apt-get install software-properties-commonCode language: JavaScript (javascript)
Add the Docker PPA and update package lists:
sudo add-apt-repository ppa:dotcloud/lxc-docker
sudo apt-get updateCode language: JavaScript (javascript)
Install Docker:
sudo apt-get install lxc-dockerCode language: JavaScript (javascript)
Pull a base Ubuntu image to use as your starting point:
docker pull base
Docker will download the image layers and confirm when the pull is complete. If the Docker daemon is not already running, start it in the background:
sudo docker -d &
You can list all images available locally at any time:
docker images
After pulling the base image you should see output similar to:
REPOSITORY TAG ID CREATED
base ubuntu-quantal b750fe79269d 10 weeks ago
base latest b750fe79269d 10 weeks ago
base ubuntu-12.10 b750fe79269d 10 weeks agoCode language: CSS (css)
Step 2: Install Java Inside a Docker Container
Start an interactive shell inside the base container:
docker run -i -t base /bin/bash
Docker creates a new container, assigns it a unique ID and an internal IP address, and drops you into a root shell:
root@298af82e71ef:/#Code language: PHP (php)
Inside the container, install the prerequisite package and add the Java PPA:
apt-get install software-properties-common
add-apt-repository ppa:webupd8team/java
apt-get updateCode language: JavaScript (javascript)
Install Java (you may be prompted to accept the Oracle license agreement):
apt-get install git curl oracle-java7-installerCode language: JavaScript (javascript)
Exit the container with exit or Ctrl+D, then list all containers (including stopped ones) to find its ID:
docker ps -a
ID IMAGE COMMAND CREATED STATUS
8e07a84ea97a base:latest /bin/bash 12 minutes ago Exit 0
...Code language: PHP (php)
Commit the most recent container (the one where you installed Java) as a new named image:
docker commit 8e07a84ea97a durdn/java7
Verify it appears in your local image list:
docker images
REPOSITORY TAG ID CREATED
durdn/java7 latest ab6396541f9a 2 hours ago
base latest b750fe79269d 10 weeks ago
You now have a reusable Docker image with Java installed. Any future container launched from durdn/java7 starts with Java already present.
Step 3: Install the Java Application in Your Container
Spin up an interactive shell from your new durdn/java7 image. Map container port 7990 to the host and declare a persistent volume at /opt/stash-home for application data:
docker run -i -t -p :7990 -v /opt/stash-home durdn/java7 /bin/bash
Inside the container, download Bitbucket Server (formerly called Stash — hence the file and directory names below):
curl -Lks https://www.atlassian.com/software/stash/downloads/binary/atlassian-bitbucket-4.13.0.tar.gz \
-o /root/stash.tar.gzCode language: JavaScript (javascript)
Unpack the archive and set up the application home directory:
mkdir /opt/stash
tar zxf /root/stash.tar.gz --strip=1 -C /opt/stash
mkdir /opt/stash-home
export STASH_HOME=/opt/stash-homeCode language: JavaScript (javascript)
Add the container’s hostname to /etc/hosts to avoid startup errors (replace 298af82e71ef with your actual container ID as shown in the shell prompt):
echo "127.0.0.1 298af82e71ef" >> /etc/hostsCode language: JavaScript (javascript)
Start the application in the foreground:
/opt/stash/bin/start-stash.sh -fg
Open http://localhost:7990/stash in your browser to confirm the application is running.
Exit the container, then commit the state to a new image:
docker commit aec2feb8cdea durdn/stash
You now have a durdn/stash image with Bitbucket Server installed and ready to launch.
Ephemeral Containers and Persistent Storage
Docker containers are ephemeral: when a container is removed, its writable layer is discarded and the container resets to its image state. Without persistence, application data is lost every time you stop and remove a container.
The solution is Docker volumes. Volumes exist independently of any individual container and can be shared between containers. In the older Docker API shown here, the --volumes-from flag reuses the volume from a previously run container.
Start Bitbucket Server interactively, reusing the data volume from the earlier container:
docker run -i -t -p :7990 --volumes-from aec2feb8cdea durdn/stash \
/bin/bash -c 'STASH_HOME=/opt/stash-home /opt/stash/bin/start-stash.sh -fg'Code language: JavaScript (javascript)
To run the container in the background (detached mode), replace -i -t with -d:
docker run -d -p :7990 --volumes-from aec2feb8cdea durdn/stash \
/bin/bash -c 'STASH_HOME=/opt/stash-home /opt/stash/bin/start-stash.sh -fg'Code language: JavaScript (javascript)
Confirm the container is running:
docker ps
Your application is now running in the background with data persisted across container restarts.
Modern Alternatives: Dockerfiles and Official Images
The interactive docker commit workflow demonstrated above is useful for understanding how Docker layers work, but production workflows typically use Dockerfiles. A Dockerfile codifies every step as a reproducible build instruction, making images auditable and easy to rebuild.
For Java applications, Docker Hub provides official images such as eclipse-temurin and amazoncorretto that eliminate the need to manually install a JDK. A minimal Dockerfile for a Java application might look like this:
# Use an official JDK base image
FROM eclipse-temurin:21-jre
# Copy the application JAR into the image
COPY target/myapp.jar /app/myapp.jar
# Expose the application port
EXPOSE 8080
# Run the application
CMD ["java", "-jar", "/app/myapp.jar"]Code language: PHP (php)
Build the image with:
docker build -t myapp:latest .Code language: CSS (css)
And run it with a named volume for data persistence:
docker run -d -p 8080:8080 -v myapp-data:/data myapp:latest
Key Takeaways
- Docker containers provide a portable, repeatable runtime environment for Java applications without requiring configuration changes on the host system.
- The
docker commitapproach turns a running container into a reusable image — useful for learning, but Dockerfiles are the standard for production builds. - Containers are ephemeral by default. Use Docker volumes (
-vor--volumes-from) to persist data between container runs. - For modern Java workloads, start from an official JDK image on Docker Hub rather than installing Java manually inside a generic base image.
- Use
docker psto list running containers anddocker ps -ato include stopped ones.