From Local Builds to Cloud Deployment

  • Home
  • Blog
  • From Local Builds to Cloud Deployment
Learning By Kartikeya rajulapati 07 Jun 2026 06:47 PM
From Local Builds to Cloud Deployment
Learn how Docker containers can streamline your development workflow, eliminate β€œit works on my machine” problems, and make deploying applications faster and more consistent.

πŸ’Ύ Docker Volumes & Data Persistence

A complete, hands-on guide to keeping your data safe even when containers are deleted β€” with 5 real-world labs you can copy-paste and run right now!

πŸ“… June 16, 2026🎯 Beginner β†’ Intermediate⏱️ 25 min read🏷️ Docker, DevOpsπŸ§ͺ 5 Hands-On Labs

1😱 The Problem β€” Why Container Data Gets Lost

Before we dive into volumes, let's understand why this problem exists. Docker containers are designed to be ephemeral β€” meaning they are temporary by nature. This is a feature, not a bug! But it creates a serious challenge when you need to store data.

⚠️ Critical Rule to Remember

Every time you delete a Docker container,ALL data stored inside is permanently deleted. This includes databases, uploaded files, logs, and configuration changes.

πŸ”΄ Real-World Problems This Causes

  • Database Data Lost: You run MySQL in a container, insert 10,000 records, delete the container β€” all records gone forever!
  • Uploaded Files Lost: Users upload profile pictures to your app container β€” you restart the container β€” all images deleted!
  • Config Changes Lost: You edit nginx.conf inside a container β€” restart it β€” back to default settings!
  • Session Data Lost: User login sessions stored inside container β€” container crashes β€” all users logged out!

βœ… The Solution β€” Docker Volumes

Docker Volumes store data OUTSIDE the container on the host machine. Data lives independently of the container lifecycle β€” even if you delete, recreate, or update your container, the data remains safe.

βœ… Golden Rule

Always use volumes for any data you care about β€” especiallydatabases, user uploads, configuration files, and logs.

2🧠 Easy Analogy β€” Hotel Room & Locker

Let's make this super easy to understand with a real-world analogy before touching any commands.

🏨

Hotel Room = Container

You check into a hotel room (start a container). When you check out (delete the container), the hotel staff cleans everything β€” all your stuff is gone!

πŸ”

Hotel Locker = Named Volume

The hotel has a personal locker (Docker Volume) for your valuables. Even after you check out and check back in, your valuables are still in the locker!

πŸ—‚οΈ

Bring Your Folder = Bind Mount

You bring your own folder from home (your PC's folder) into the hotel room. Edit files in the room and they update on your PC too β€” live two-way sync!

πŸ’‘ Key Insight

A Docker Volume is just afolder on your host machinethat Docker manages. When you mount it into a container, the container reads/writes to that folder β€” data persists even after the container is gone.

3πŸ“¦ 3 Types of Docker Storage

Docker provides three different mechanisms for persisting data. Each has its own use case, advantages, and limitations.

πŸ’Ύ

1. Named Volumes

Fully managed by Docker. Stored in Docker's own directory. Best for production databases and persistent app data.

-v mydata:/app/data
πŸ“‚

2. Bind Mounts

You specify an exact path on your host machine. Changes reflect instantly. Best for development with live code reload.

-v /home/user/app:/app
⚑

3. tmpfs Mounts

Stored in host RAM. Extremely fast but lost on container stop. Best for temporary/sensitive data like tokens.

--tmpfs /app/temp

πŸ“ Where is Data Stored on Host?

πŸ“ Storage Locationsbash
# Named Volume location (Linux)
/var/lib/docker/volumes//_data

# Named Volume location (Windows WSL2)
\\wsl$\docker-desktop-data\data\docker\volumes\

# Bind Mount - you choose the exact path
/home/youruser/myproject   (any path you specify)

# tmpfs - stored in RAM only, no disk path
RAM only - no persistent path on disk

4πŸ§ͺ Lab 1 β€” Prove Data Loss Without Volumes

Before learning how to fix the problem, let's prove the problem exists. This lab shows exactly what happens to data when a container is deleted.

⚠️ Prerequisites

Make sure Docker is installed and running. Run docker --version to verify. You need Docker 20.x or higher.

πŸ§ͺ Lab 1 β€” Data Loss Demonstration
1

Start an Ubuntu container and create a file inside it

We will create important data inside a container without a volume

Step 1 β€” Create Data Inside Containerbash<button class="copy-btn" style="background-color:rgb(22, 27, 34);border-radius:8px;border:1px solid rgb(48, 54, 61);box-sizing:border-box;color:rgb(139, 148, 158);cursor:pointer;font-size:12px;margin:0px;padding:4px 12px;transition:0.2s;" onclick="copyCode(this)">πŸ“‹ Copy</button>
# Start an Ubuntu container interactively
docker run -it --name test-container ubuntu bash

# Inside the container - create a file with important data
echo "This is important data - will it survive?" > /tmp/mydata.txt
echo "Record 1: Alice" >> /tmp/mydata.txt
echo "Record 2: Bob" >> /tmp/mydata.txt

# Verify the file exists
cat /tmp/mydata.txt

# Expected output:
This is important data - will it survive?
Record 1: Alice
Record 2: Bob

# Exit the container
exit
2

Delete the container and try to recover data

Now delete the container and see what happens to the data

Step 2 β€” Delete Container & Try to Recoverbash<button class="copy-btn" style="background-color:rgb(22, 27, 34);border-radius:8px;border:1px solid rgb(48, 54, 61);box-sizing:border-box;color:rgb(139, 148, 158);cursor:pointer;font-size:12px;margin:0px;padding:4px 12px;transition:0.2s;" onclick="copyCode(this)">πŸ“‹ Copy</button>
# Delete the container
docker rm test-container

# Start a brand new Ubuntu container
docker run -it --name test-container-2 ubuntu bash

# Try to read the file - it's GONE!
cat /tmp/mydata.txt

# Expected output (ERROR):
cat: /tmp/mydata.txt: No such file or directory

# Data is permanently lost! Exit and cleanup
exit
docker rm test-container-2

❌ Result: Data is Gone Forever!

The file we created is completely gone after container deletion. This is exactly the problem Docker Volumes solve. Let's fix this in Lab 2!

5πŸ§ͺ Lab 2 β€” Named Volumes (MySQL Persistence)

This is the most important lab. We'll run MySQL with a named volume, insert data, delete the container, recreate it, and prove the data survived!

πŸ§ͺ Lab 2 β€” MySQL with Named Volume
1

Create a named volume for MySQL data

Step 1 β€” Create Named Volumebash<button class="copy-btn" style="background-color:rgb(22, 27, 34);border-radius:8px;border:1px solid rgb(48, 54, 61);box-sizing:border-box;color:rgb(139, 148, 158);cursor:pointer;font-size:12px;margin:0px;padding:4px 12px;transition:0.2s;" onclick="copyCode(this)">πŸ“‹ Copy</button>
# Create a named volume for MySQL data
docker volume create mysql-data

# Verify it was created
docker volume ls

# Expected output:
DRIVER    VOLUME NAME
local     mysql-data

# Inspect the volume - see where Docker stores it
docker volume inspect mysql-data

# Expected output:
[
    {
        "CreatedAt": "2026-06-16T...",
        "Driver": "local",
        "Mountpoint": "/var/lib/docker/volumes/mysql-data/_data",
        "Name": "mysql-data",
        "Scope": "local"
    }
]
2

Run MySQL container with the volume attached

Step 2 β€” Run MySQL with Volumebash<button class="copy-btn" style="background-color:rgb(22, 27, 34);border-radius:8px;border:1px solid rgb(48, 54, 61);box-sizing:border-box;color:rgb(139, 148, 158);cursor:pointer;font-size:12px;margin:0px;padding:4px 12px;transition:0.2s;" onclick="copyCode(this)">πŸ“‹ Copy</button>
# Run MySQL with the named volume attached to its data directory
docker run -d \
  --name my-mysql \
  -v mysql-data:/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=MySecret123 \
  -e MYSQL_DATABASE=testdb \
  -p 3306:3306 \
  mysql:8.0

# Wait 20 seconds for MySQL to fully initialize
sleep 20

# Verify container is running
docker ps

# Check MySQL startup logs
docker logs my-mysql --tail 5
3

Connect to MySQL and insert data

Step 3 β€” Insert Data into MySQLsql<button class="copy-btn" style="background-color:rgb(22, 27, 34);border-radius:8px;border:1px solid rgb(48, 54, 61);box-sizing:border-box;color:rgb(139, 148, 158);cursor:pointer;font-size:12px;margin:0px;padding:4px 12px;transition:0.2s;" onclick="copyCode(this)">πŸ“‹ Copy</button>
# Connect to MySQL inside the container
docker exec -it my-mysql mysql -u root -pMySecret123

-- Switch to our test database
USE testdb;

-- Create a users table
CREATE TABLE users (
  id         INT AUTO_INCREMENT PRIMARY KEY,
  name       VARCHAR(100),
  email      VARCHAR(100),
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Insert test records
INSERT INTO users (name, email) VALUES
  ('Alice Johnson', 'alice@example.com'),
  ('Bob Smith',     'bob@example.com'),
  ('Charlie Brown', 'charlie@example.com');

-- Verify data was inserted
SELECT * FROM users;

-- Expected output:
+----+---------------+---------------------+---------------------+
| id | name          | email               | created_at          |
+----+---------------+---------------------+---------------------+
|  1 | Alice Johnson | alice@example.com   | 2026-06-16 ...      |
|  2 | Bob Smith     | bob@example.com     | 2026-06-16 ...      |
|  3 | Charlie Brown | charlie@example.com | 2026-06-16 ...      |
+----+---------------+---------------------+---------------------+

EXIT;
4

Delete the container β€” data should be SAFE!

Step 4 β€” Delete Containerbash<button class="copy-btn" style="background-color:rgb(22, 27, 34);border-radius:8px;border:1px solid rgb(48, 54, 61);box-sizing:border-box;color:rgb(139, 148, 158);cursor:pointer;font-size:12px;margin:0px;padding:4px 12px;transition:0.2s;" onclick="copyCode(this)">πŸ“‹ Copy</button>
# Force delete the MySQL container
docker rm -f my-mysql

# Verify container is gone
docker ps -a

# But check - the VOLUME still exists!
docker volume ls

# Expected output - volume is still there:
DRIVER    VOLUME NAME
local     mysql-data   <-- Still exists! Data is safe!
5

Recreate container and verify data is back!

Step 5 β€” Recreate & Verify Data Survivedbash<button class="copy-btn" style="background-color:rgb(22, 27, 34);border-radius:8px;border:1px solid rgb(48, 54, 61);box-sizing:border-box;color:rgb(139, 148, 158);cursor:pointer;font-size:12px;margin:0px;padding:4px 12px;transition:0.2s;" onclick="copyCode(this)">πŸ“‹ Copy</button>
# Start a NEW MySQL container with the SAME volume
docker run -d \
  --name my-mysql-v2 \
  -v mysql-data:/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=MySecret123 \
  -p 3306:3306 \
  mysql:8.0

# Wait for MySQL to start
sleep 20

# Connect and check if data survived!
docker exec -it my-mysql-v2 mysql -u root -pMySecret123

USE testdb;
SELECT * FROM users;

-- ALL DATA IS BACK! Volume worked perfectly!
+----+---------------+---------------------+---------------------+
| id | name          | email               | created_at          |
+----+---------------+---------------------+---------------------+
|  1 | Alice Johnson | alice@example.com   | 2026-06-16 ...      |
|  2 | Bob Smith     | bob@example.com     | 2026-06-16 ...      |
|  3 | Charlie Brown | charlie@example.com | 2026-06-16 ...      |
+----+---------------+---------------------+---------------------+

EXIT;

# Cleanup
docker rm -f my-mysql-v2

πŸŽ‰ Success! Data Survived Container Deletion!

MySQL data persisted because it was stored in the mysql-data volume, not inside the container. This is the power of Docker Volumes!

6πŸ§ͺ Lab 3 β€” Bind Mounts (Live Code Reload)

Bind mounts are perfect for development. Edit code on your local machine and changes appear instantly inside the container β€” no need to rebuild the image!

πŸ§ͺ Lab 3 β€” Node.js App with Bind Mount
1

Create a simple Node.js app on your host machine

Step 1 β€” Create App Files on Hostbash<button class="copy-btn" style="background-color:rgb(22, 27, 34);border-radius:8px;border:1px solid rgb(48, 54, 61);box-sizing:border-box;color:rgb(139, 148, 158);cursor:pointer;font-size:12px;margin:0px;padding:4px 12px;transition:0.2s;" onclick="copyCode(this)">πŸ“‹ Copy</button>
# Create project directory on your HOST machine
mkdir ~/docker-bind-demo
cd ~/docker-bind-demo

# Create a simple Node.js HTTP server
cat > app.js << 'EOF'
const http = require('http');
const server = http.createServer((req, res) => {
  res.writeHead(200, {'Content-Type': 'text/html'});
  res.end('

Hello from Docker Bind Mount!

Version 1.0

'); }); server.listen(3000, () => console.log('Server running on port 3000')); EOF # Create package.json cat > package.json << 'EOF' { "name": "bind-mount-demo", "version": "1.0.0", "main": "app.js", "scripts": { "start": "node app.js" } } EOF # Verify files are created ls -la
2

Run Node.js container with bind mount

Step 2 β€” Run Container with Bind Mountbash<button class="copy-btn" style="background-color:rgb(22, 27, 34);border-radius:8px;border:1px solid rgb(48, 54, 61);box-sizing:border-box;color:rgb(139, 148, 158);cursor:pointer;font-size:12px;margin:0px;padding:4px 12px;transition:0.2s;" onclick="copyCode(this)">πŸ“‹ Copy</button>
# Run Node.js container with bind mount
# $(pwd) = current directory on your host machine
# /app   = where it appears inside the container
docker run -d \
  --name node-app \
  -v $(pwd):/app \
  -w /app \
  -p 3000:3000 \
  node:18 node app.js

# Test the app
curl http://localhost:3000

# Expected output:

Hello from Docker Bind Mount!

Version 1.0

3

Edit code on host β€” see it change LIVE (no rebuild!)

Step 3 β€” Live Code Update (No Rebuild!)bash<button class="copy-btn" style="background-color:rgb(22, 27, 34);border-radius:8px;border:1px solid rgb(48, 54, 61);box-sizing:border-box;color:rgb(139, 148, 158);cursor:pointer;font-size:12px;margin:0px;padding:4px 12px;transition:0.2s;" onclick="copyCode(this)">πŸ“‹ Copy</button>
# Edit app.js on your HOST machine
cat > ~/docker-bind-demo/app.js << 'EOF'
const http = require('http');
const server = http.createServer((req, res) => {
  res.writeHead(200, {'Content-Type': 'text/html'});
  res.end('

UPDATED! Bind Mount Works!

Version 2.0 - No rebuild!

'); }); server.listen(3000, () => console.log('Server v2.0 running')); EOF # Restart the container to pick up changes docker restart node-app # Test again - new version is live! curl http://localhost:3000 # Expected output:

UPDATED! Bind Mount Works!

Version 2.0 - No rebuild!

# Cleanup docker rm -f node-app

πŸ’‘ Windows Users β€” Bind Mount Path Format

On Windows: -v C:/Users/YourName/project:/app Β |Β  With WSL2: -v /mnt/c/Users/YourName/project:/app

7πŸ§ͺ Lab 4 β€” tmpfs Mount (RAM Storage)

tmpfs mounts store data in host RAM. Extremely fast but lost when container stops. Perfect for sensitive temporary data like session tokens or secrets.

πŸ§ͺ Lab 4 β€” tmpfs Mount Demo
tmpfs Mount β€” Full Demobash<button class="copy-btn" style="background-color:rgb(22, 27, 34);border-radius:8px;border:1px solid rgb(48, 54, 61);box-sizing:border-box;color:rgb(139, 148, 158);cursor:pointer;font-size:12px;margin:0px;padding:4px 12px;transition:0.2s;" onclick="copyCode(this)">πŸ“‹ Copy</button>
# Run container with tmpfs mount at /app/temp (100MB RAM)
docker run -d \
  --name tmpfs-demo \
  --tmpfs /app/temp:rw,size=100m \
  ubuntu sleep 3600

# Write sensitive data to the tmpfs mount
docker exec tmpfs-demo bash -c \
  "echo 'SECRET_TOKEN=abc123xyz987' > /app/temp/session.txt"

# Read the data - it's there!
docker exec tmpfs-demo cat /app/temp/session.txt

# Expected output:
SECRET_TOKEN=abc123xyz987

# Verify it's stored in RAM (not on disk)
docker exec tmpfs-demo df -h /app/temp

# Expected output (shows tmpfs filesystem):
Filesystem      Size  Used Avail Use% Mounted on
tmpfs           100M  4.0K  100M   1% /app/temp

# Stop the container
docker stop tmpfs-demo

# Start it again
docker start tmpfs-demo

# Try to read the file - it's GONE (expected behavior!)
docker exec tmpfs-demo cat /app/temp/session.txt

# Expected output:
cat: /app/temp/session.txt: No such file or directory
# This is CORRECT - tmpfs data is intentionally temporary!

# Cleanup
docker rm -f tmpfs-demo

8πŸ§ͺ Lab 5 β€” Docker Compose with Volumes

In real projects, you'll use Docker Compose to manage multi-container apps. Here's how to define volumes in a docker-compose.yml file.

πŸ§ͺ Lab 5 β€” WordPress + MySQL with Compose Volumes
1

Create the docker-compose.yml file

docker-compose.ymlyaml<button class="copy-btn" style="background-color:rgb(22, 27, 34);border-radius:8px;border:1px solid rgb(48, 54, 61);box-sizing:border-box;color:rgb(139, 148, 158);cursor:pointer;font-size:12px;margin:0px;padding:4px 12px;transition:0.2s;" onclick="copyCode(this)">πŸ“‹ Copy</button>
# Create project directory
mkdir ~/wordpress-demo && cd ~/wordpress-demo

# Create docker-compose.yml
cat > docker-compose.yml << 'EOF'
version: '3.8'

services:

  db:
    image: mysql:8.0
    container_name: wordpress-db
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: rootpassword123
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wpuser
      MYSQL_PASSWORD: wppassword123
    volumes:
      - mysql-data:/var/lib/mysql       # Named volume for DB
    networks:
      - wp-network

  wordpress:
    image: wordpress:latest
    container_name: wordpress-app
    restart: always
    depends_on:
      - db
    ports:
      - "8080:80"
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wpuser
      WORDPRESS_DB_PASSWORD: wppassword123
      WORDPRESS_DB_NAME: wordpress
    volumes:
      - wp-content:/var/www/html/wp-content              # Named volume
      - ./themes:/var/www/html/wp-content/themes/custom  # Bind mount
    networks:
      - wp-network

volumes:
  mysql-data:
    driver: local
  wp-content:
    driver: local

networks:
  wp-network:
    driver: bridge
EOF
2

Start the stack and verify volumes

Step 2 β€” Start & Verifybash<button class="copy-btn" style="background-color:rgb(22, 27, 34);border-radius:8px;border:1px solid rgb(48, 54, 61);box-sizing:border-box;color:rgb(139, 148, 158);cursor:pointer;font-size:12px;margin:0px;padding:4px 12px;transition:0.2s;" onclick="copyCode(this)">πŸ“‹ Copy</button>
# Start all services in background
docker compose up -d

# Check all containers are running
docker compose ps

# Expected output:
NAME             IMAGE         STATUS          PORTS
wordpress-db     mysql:8.0     Up 30 seconds   3306/tcp
wordpress-app    wordpress     Up 25 seconds   0.0.0.0:8080->80/tcp

# List volumes created by compose
docker volume ls

# Expected output:
DRIVER    VOLUME NAME
local     wordpress-demo_mysql-data
local     wordpress-demo_wp-content

# Visit WordPress: http://localhost:8080

# Stop containers - volumes are KEPT!
docker compose down

# Restart - all data is still there!
docker compose up -d

# To remove EVERYTHING including volumes (careful!):
docker compose down -v

9βš™οΈ Essential Volume Commands Reference

πŸ’Ύ Complete Volume Command Referencebash<button class="copy-btn" style="background-color:rgb(22, 27, 34);border-radius:8px;border:1px solid rgb(48, 54, 61);box-sizing:border-box;color:rgb(139, 148, 158);cursor:pointer;font-size:12px;margin:0px;padding:4px 12px;transition:0.2s;" onclick="copyCode(this)">πŸ“‹ Copy All</button>
# ─── CREATE ────────────────────────────────────────────────

# Create a basic named volume
docker volume create mydata

# Create volume with custom driver options
docker volume create --driver local \
  --opt type=none \
  --opt device=/data/myapp \
  --opt o=bind \
  mydata-custom

# ─── LIST & INSPECT ────────────────────────────────────────

# List all volumes
docker volume ls

# List only dangling (unused) volumes
docker volume ls --filter dangling=true

# Inspect a volume (full details)
docker volume inspect mydata

# Get just the mount path
docker volume inspect --format '{{.Mountpoint}}' mydata

# ─── MOUNT IN CONTAINERS ───────────────────────────────────

# Named volume - short syntax
docker run -v mydata:/app/data myimage

# Named volume - long --mount syntax (recommended)
docker run --mount type=volume,source=mydata,target=/app/data myimage

# Bind mount - short syntax
docker run -v /host/path:/container/path myimage

# Bind mount - long --mount syntax
docker run --mount type=bind,source=/host/path,target=/container/path myimage

# Read-only bind mount (container cannot write to it)
docker run -v /host/config:/app/config:ro myimage

# tmpfs mount (RAM-based, 64MB limit)
docker run --tmpfs /app/temp:rw,size=64m myimage

# ─── BACKUP & RESTORE ──────────────────────────────────────

# Backup a volume to a tar.gz file
docker run --rm \
  -v mydata:/source:ro \
  -v $(pwd):/backup \
  ubuntu tar czf /backup/mydata-backup.tar.gz -C /source .

# Restore a volume from a tar.gz file
docker run --rm \
  -v mydata-restored:/target \
  -v $(pwd):/backup:ro \
  ubuntu tar xzf /backup/mydata-backup.tar.gz -C /target

# ─── CLEANUP ───────────────────────────────────────────────

# Remove a specific volume (must not be in use)
docker volume rm mydata

# Remove multiple volumes at once
docker volume rm vol1 vol2 vol3

# Remove ALL unused volumes (interactive prompt)
docker volume prune

# Remove unused volumes without confirmation
docker volume prune -f

# See volume mounts of a running container
docker inspect  --format '{{json .Mounts}}'

10πŸ“Š Comparison Table

FeatureπŸ’Ύ Named VolumeπŸ“‚ Bind Mount⚑ tmpfs
Managed by Docker

βœ… Yes

❌ No

βœ… Yes

Data survives restart

βœ… Yes

βœ… Yes

❌ No

Data survives container delete

βœ… Yes

βœ… Yes

❌ No

Best for Production

βœ… Yes

⚠️ Maybe

❌ No

Best for Development

⚠️ Maybe

βœ… Yes

❌ No

Live code reload

❌ No

βœ… Yes

❌ No

Stored in RAM

❌ No

❌ No

βœ… Yes (Fast!)

Easy to backup

βœ… Yes

βœ… Yes

❌ No

Works on all OS

βœ… Yes

⚠️ Path varies

❌ Linux only

Shareable between containers

βœ… Yes

βœ… Yes

❌ No

11

Interested in our training?

To stay updated with regular alerts via email, subscribing is quick and hassle-free. Please fill out the subscription form below to receive timely updates. By providing your information, you will be added to our mailing list, keeping you informed about the latest news and announcements