A Docker Volume is the preferred mechanism for persisting data generated by and used by Docker containers.
Containers are designed to be ephemeral (temporary and disposable). Any data written directly to a container's filesystem layer is lost when the container is removed. A volume solves this by providing a storage location that exists on the host machine but is managed by Docker, ensuring the data remains intact even if the containers are stopped, removed, or replaced.
in Simple terms, Docker Volume is a special storage mechanism in Docker to store data outside the container’s filesystem, so the data is not lost when a container stops, restarts, or is removed.
By default, the data modified or created within a container is stored in container's writable layer
Key Characteristics
Persistence: Data stored in a volume survives the removal and recreation of the container that created it.
Docker Management: Volumes are created and managed via the Docker CLI (docker volume create, docker volume ls, etc.). On Linux, they are typically stored under a directory managed by Docker (usually /var/lib/docker/volumes/).
Data Sharing: The same volume can be safely mounted into multiple running containers simultaneously, allowing them to share data.
Portability: Volumes are independent of the host's specific directory structure, making them easier to migrate and back up across different environments (Linux, Windows, macOS).
🗃️ Types of Docker Storage Mounts
Docker provides a few types of storage mounts, but Volumes are generally recommended for production data
| Mount Type | Description | Best Use Case |
| Named Volumes | The most common type. They are created with a specific name (e.g., db_data) and fully managed by Docker. | Production data persistence (databases, application state, logs). |
| Anonymous Volumes | Similar to named volumes but are not given an explicit name; Docker assigns them a unique random ID. | When data persistence is needed, but the volume doesn't need to be referenced or reused explicitly. |
| Bind Mounts | Maps a specific file or directory on the host machine (defined by an absolute path) directly into the container. Docker does not manage the host location. | Development work, where you need to immediately see code changes reflected in the container. |
Jenkins will be our example application for illustrating volume persistence.
[root@devopsvm01 ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest d261fd19cb63 Less than a second ago 152MB
mahekarthya/my-nginx-site version-1 5fbef17336dd 3 hours ago 48.3MB
ubuntu latest 97bed23a3497 4 weeks ago 78.1MB
hello-world latest 1b44b5a3e06a 2 months ago 10.1kB
[root@devopsvm01 ~]#
[root@devopsvm01 ~]#
Pull the latest Jenkins image.
[root@devopsvm01 ~]# docker pull jenkins/jenkins:2.528.2-lts-jdk21
2.528.2-lts-jdk21: Pulling from jenkins/jenkins
13cc39f8244a: Pull complete
dc2a77f462ea: Pull complete
33300af18dd0: Pull complete
c27509c3e53b: Pull complete
e4beac64dffa: Pull complete
a37b858bb47a: Pull complete
744b4792e083: Pull complete
05a7d9a8b608: Pull complete
8d2a75b252b2: Pull complete
65e4ba8066bc: Pull complete
5dc07232677a: Pull complete
7718ff514022: Pull complete
Digest: sha256:7b1c378278279c8688efd6168c25a1c2723a6bd6f0420beb5ccefabee3cc3bb1
Status: Downloaded newer image for jenkins/jenkins:2.528.2-lts-jdk21
docker.io/jenkins/jenkins:2.528.2-lts-jdk21
[root@devopsvm01 ~]#
[root@devopsvm01 ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
jenkins/jenkins 2.528.2-lts-jdk21 aa2bbbd632f3 Less than a second ago 490MB
nginx latest d261fd19cb63 Less than a second ago 152MB
mahekarthya/my-nginx-site version-1 5fbef17336dd 3 hours ago 48.3MB
ubuntu latest 97bed23a3497 4 weeks ago 78.1MB
hello-world latest 1b44b5a3e06a 2 months ago 10.1kB
[root@devopsvm01 ~]#
Create a volume.
[root@devopsvm01 ~]# docker volume ls
DRIVER VOLUME NAME
[root@devopsvm01 ~]#
[root@devopsvm01 ~]# docker volume create Jenkins-Vol
Jenkins-Vol
[root@devopsvm01 ~]# docker volume ls
DRIVER VOLUME NAME
local Jenkins-Vol
[root@devopsvm01 ~]# docker volume inspect Jenkins-Vol
[
{
"CreatedAt": "2025-11-03T15:03:30+05:30",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/Jenkins-Vol/_data",
"Name": "Jenkins-Vol",
"Options": null,
"Scope": "local"
}
]
[root@devopsvm01 ~]# cd /var/lib/docker/volumes/Jenkins-Vol/_data
[root@devopsvm01 _data]# ls -lrt
total 0
[root@devopsvm01 _data]#
By default, Docker stores volumes in the directory /var/lib/docker/volumes/ on the host machine.
When you created the named volume Jenkins-Vol, Docker automatically created and manages the corresponding directory on your host file system at the path specified by the Mountpoint field:
/var/lib/docker/volumes/Jenkins-Vol/_data
This directory on your host is where all the persistent data for your Jenkins container will be stored.
Spin up a new Jenkin container using the newly created volume.
Before creating a new container, we need to map Jenkins home to a Docker volume. For that, we must know the default Jenkins home directory
[root@devopsvm01 _data]# docker inspect jenkins/jenkins:2.528.2-lts-jdk21 | grep -i '"JENKINS_HOME'
"JENKINS_HOME=/var/jenkins_home",
[root@devopsvm01 _data]#
So Jenkins store the workspace in /var/jenkins_home. All Jenkins data lives in there - including plugins and configuration.
docker run --name Jenkins1 -v Jenkins-Vol:/var/jenkins_home -p 8080:8080 -p 50000:50000 jenkins/jenkins:2.528.2-lts-jdk21
[root@devopsvm01 _data]# docker ps -a | grep -i "Up"
cd6834b2e669 jenkins/jenkins:2.528.2-lts-jdk21 "/usr/bin/tini -- /u…" 50 minutes ago Up 50 minutes 0.0.0.0:8080->8080/tcp, [::]:8080->8080/tcp, 0.0.0.0:50000->50000/tcp, [::]:50000->50000/tcp Jenkins1
[root@devopsvm01 _data]#
The persistent Jenkins configuration and plugin data are now stored in the host directory, /var/lib/docker/volumes/Jenkins-Vol/_data.
[root@devopsvm01 _data]# pwd
/var/lib/docker/volumes/Jenkins-Vol/_data
[root@devopsvm01 _data]# ls -lhrt
total 52K
drwxr-xr-x. 10 maheshpayyan maheshpayyan 4.0K Nov 3 15:38 war
-rw-r--r--. 1 maheshpayyan maheshpayyan 64 Nov 3 15:38 secret.key
-rw-r--r--. 1 maheshpayyan maheshpayyan 0 Nov 3 15:38 secret.key.not-so-secret
drwxr-xr-x. 2 maheshpayyan maheshpayyan 6 Nov 3 15:38 plugins
-rw-r--r--. 1 maheshpayyan maheshpayyan 171 Nov 3 15:38 jenkins.telemetry.Correlator.xml
drwxr-xr-x. 2 maheshpayyan maheshpayyan 24 Nov 3 15:38 userContent
drwxr-xr-x. 3 maheshpayyan maheshpayyan 84 Nov 3 15:38 users
drwxr-xr-x. 2 maheshpayyan maheshpayyan 67 Nov 3 15:38 updates
-rw-r--r--. 1 maheshpayyan maheshpayyan 179 Nov 3 16:07 jenkins.model.JenkinsLocationConfiguration.xml
-rw-r--r--. 1 maheshpayyan maheshpayyan 7 Nov 3 16:08 jenkins.install.UpgradeWizard.state
drwxr-xr-x. 3 maheshpayyan maheshpayyan 21 Nov 3 16:26 workspace
drwx------. 2 maheshpayyan maheshpayyan 4.0K Nov 3 16:26 secrets
-rw-r--r--. 1 maheshpayyan maheshpayyan 258 Nov 3 16:27 queue.xml.bak
-rw-r--r--. 1 maheshpayyan maheshpayyan 216 Nov 3 16:31 copy_reference_file.log
-rw-r--r--. 1 maheshpayyan maheshpayyan 156 Nov 3 16:31 hudson.model.UpdateCenter.xml
-rw-r--r--. 1 maheshpayyan maheshpayyan 7 Nov 3 16:31 jenkins.install.InstallUtil.lastExecVersion
-rw-r--r--. 1 maheshpayyan maheshpayyan 1.1K Nov 3 16:31 nodeMonitors.xml
-rw-r--r--. 1 maheshpayyan maheshpayyan 1.7K Nov 3 16:31 config.xml
-rw-r--r--. 1 maheshpayyan maheshpayyan 258 Nov 3 16:44 queue.xml
drwxr-xr-x. 2 maheshpayyan maheshpayyan 6 Nov 3 16:48 jobs
[root@devopsvm01 _data]#
Note down the admin password and login to Jenkins console
Select Plugins to install option
Select none and finish the installation.
Skip and continue as admin
Finish
Create a sample job on this container
Give some name
Under build step, select execute a shell
Let's build the job
Job ran successful.
Now spin up another instance of Jenkins using the same volume that we used earlier
docker run --name Jenkins2 -v Jenkins-Vol:/var/jenkins_home -p 9080:8080 -p 60000:50000 jenkins/jenkins:2.528.2-lts-jdk21
[root@devopsvm01 ~]# docker ps -a | grep -i "Up"
cd6834b2e669 jenkins/jenkins:2.528.2-lts-jdk21 "/usr/bin/tini -- /u…" 52 minutes ago Up 52 minutes 0.0.0.0:8080->8080/tcp, [::]: 8080->8080/tcp, 0.0.0.0:50000->50000/tcp, [::]:50000->50000/tcp Jenkins1
[root@devopsvm01 ~]#
[root@devopsvm01 ~]# docker run --name Jenkins2 -v Jenkins-Vol:/var/jenkins_home -p 9080:8080 -p 60000:50000 jenkins/jenkins:2.528.2-lts-jdk21
Running from: /usr/share/jenkins/jenkins.war
webroot: /var/jenkins_home/war
2025-11-03 11:01:09.787+0000 [id=1] INFO winstone.Logger#logInternal: Beginning extraction from war file
2025-11-03 11:01:10.188+0000 [id=1] WARNING o.e.j.ee9.nested.ContextHandler#setContextPath: Empty contextPath
2025-11-03 11:01:10.557+0000 [id=1] INFO org.eclipse.jetty.server.Server#doStart: jetty-12.0.25; built: 2025-08-11T23:52:37.219Z; git: a862b76d8372e2 4205765182d9ae1d1d333ce2ea; jvm 21.0.9+10-LTS
2025-11-03 11:01:13.624+0000 [id=1] INFO o.e.j.e.w.StandardDescriptorProcessor#visitServlet: NO JSP Support for /, did not find org.eclipse.jetty.ee9.jsp.JettyJspServlet
2025-11-03 11:01:14.402+0000 [id=1] INFO o.e.j.s.DefaultSessionIdManager#doStart: Session workerName=node0
2025-11-03 11:01:16.804+0000 [id=1] INFO hudson.WebAppMain#contextInitialized: Jenkins home directory: /var/jenkins_home found at: EnvVars.masterEnvVars.get("JENKINS_HOME")
2025-11-03 11:01:17.687+0000 [id=1] INFO o.e.j.s.handler.ContextHandler#doStart: Started oeje9n.ContextHandler$CoreContextHandler@1c12f3ee{Jenkins v2.528.2,/,b=file:///var/jenkins_home/war/,a=AVAILABLE,h=oeje9n.ContextHandler$CoreContextHandler$CoreToNestedHandler@6d467c87{STARTED}}
2025-11-03 11:01:17.830+0000 [id=1] INFO o.e.j.server.AbstractConnector#doStart: Started ServerConnector@2b97a809{HTTP/1.1, (http/1.1)}{0.0.0.0:8080}
2025-11-03 11:01:17.932+0000 [id=1] INFO org.eclipse.jetty.server.Server#doStart: Started oejs.Server@4a883b15{STARTING}[12.0.25,sto=0] @11748ms
2025-11-03 11:01:17.953+0000 [id=25] INFO winstone.Logger#logInternal: Winstone Servlet Engine running: controlPort=disabled
2025-11-03 11:01:18.771+0000 [id=24] INFO jenkins.model.Jenkins#<init>: Starting version 2.528.2
2025-11-03 11:01:20.076+0000 [id=31] INFO jenkins.InitReactorRunner$1#onAttained: Started initialization
2025-11-03 11:01:20.401+0000 [id=30] INFO jenkins.InitReactorRunner$1#onAttained: Listed all plugins
2025-11-03 11:01:26.311+0000 [id=30] INFO jenkins.InitReactorRunner$1#onAttained: Prepared all plugins
2025-11-03 11:01:26.330+0000 [id=30] INFO jenkins.InitReactorRunner$1#onAttained: Started all plugins
2025-11-03 11:01:26.341+0000 [id=31] INFO jenkins.InitReactorRunner$1#onAttained: Augmented all extensions
2025-11-03 11:01:28.337+0000 [id=31] INFO jenkins.InitReactorRunner$1#onAttained: System config loaded
2025-11-03 11:01:28.346+0000 [id=30] INFO jenkins.InitReactorRunner$1#onAttained: System config adapted
2025-11-03 11:01:28.473+0000 [id=30] INFO jenkins.InitReactorRunner$1#onAttained: Loaded all jobs
2025-11-03 11:01:28.477+0000 [id=31] INFO jenkins.InitReactorRunner$1#onAttained: Configuration for all jobs updated
2025-11-03 11:01:29.538+0000 [id=31] INFO jenkins.InitReactorRunner$1#onAttained: Completed initialization
2025-11-03 11:01:30.021+0000 [id=24] INFO hudson.lifecycle.Lifecycle#onReady: Jenkins is fully up and running
When you setup second Jenkins using the same volume, you are not being prompted for plugin setup or a new password when starting Jenkins2 because you used the same volume, Jenkins-Vol, for both containers.
Verify both Jenkins uses same volume.
[root@devopsvm01 _data]# docker ps --format "{{.Names}}\t{{.Mounts}}"
Jenkins2 Jenkins-Vol
Jenkins1 Jenkins-Vol
[root@devopsvm01 _data]#
💾 Why the Setup Was Skipped
The Jenkins-Vol volume contains all the persistent data from the first run of Jenkins (Jenkins1), including:
Configuration Files: All settings, jobs, and user data.
Plugins: The list of plugins you selected and installed.
Security Files: Crucially, the initial administrative user that was set up and the file containing the final admin password.
When you ran Jenkins2 with the same volume, Jenkins saw the existing home directory (/var/jenkins_home) populated with a complete, fully configured setup. Therefore, it skipped the initial setup wizard (plugin installation and initial password generation) and immediately started using the configuration and security data already saved in the volume.
How to Access the Second Jenkins Instance
Since Jenkins2 is using the settings from Jenkins1, you need to access it using the new host ports you defined, and log in with the same administrator credentials you created during the first setup.
Access the new Jenkins URL:
Let's login and run the job that we created from Jenkins1
Check the job status and try to run
We are able to run the job from Jenkins2 as both Jenkins1 and Jenkins2 are sharing the same home.
No comments:
Post a Comment