hectoday
DocsCoursesChangelog GitHub
DocsCoursesChangelog GitHub

Access Required

Enter your access code to view courses.

Invalid code

← All courses Deploying Node.js Apps with Docker

Why Docker

  • The "Works on My Machine" Problem
  • Docker Concepts

Your First Dockerfile

  • Writing a Dockerfile
  • Multi-Stage Builds
  • The .dockerignore File

Running Containers

  • Running and Managing Containers
  • Environment Variables and Secrets
  • Health Checks

Multi-Container Apps

  • Docker Compose
  • Adding a Reverse Proxy
  • Persistent Data

Deploying to Production

  • Deploying to a VPS
  • HTTPS with Let's Encrypt
  • Zero-Downtime Deploys

CI/CD

  • Building Images in CI
  • Automated Deployment

Production Hardening

  • Container Security
  • Logging and Monitoring
  • Deployment Checklist and Capstone

Persistent Data

Containers are ephemeral

When you stop and remove a container, everything inside it is gone. The SQLite database, the uploaded files, the session store — all lost. The next docker run starts from a clean image.

This is by design. Containers are disposable. You should be able to destroy and recreate them without losing data. But the data has to live somewhere.

Docker volumes

A volume is storage that lives outside the container. It persists when the container is removed and can be attached to new containers.

# Create a named volume
docker volume create app-data

# Run a container with the volume
docker run -d \
  -v app-data:/data \
  -e DATABASE_URL=/data/app.db \
  myapp

-v app-data:/data mounts the app-data volume at /data inside the container. The SQLite database at /data/app.db persists across container restarts, stops, and removals.

Volumes in Docker Compose

services:
  app:
    build: .
    volumes:
      - app-data:/data
      - uploads:/app/uploads
    environment:
      - DATABASE_URL=/data/app.db

volumes:
  app-data: # Persists the SQLite database
  uploads: # Persists uploaded files

Named volumes are declared at the bottom of the compose file. Docker manages their location on the host filesystem.

What to put in volumes

Databases. SQLite files, PostgreSQL data directories. Without a volume, the database resets on every container restart.

Uploaded files. Files uploaded by users (from the File Uploads course). Without a volume, uploads disappear when the container restarts.

Application state. Session stores, caches, logs — anything that needs to survive a restart.

What NOT to put in volumes

Application code. The code is in the image. Mounting code via volumes is for development only (live reload). In production, the image contains the code.

node_modules. Installed inside the image during docker build. Mounting from the host causes OS-mismatch issues (same problem as .dockerignore).

Secrets. Use environment variables, not files in volumes, for secrets.

Bind mounts vs named volumes

Named volumes (-v app-data:/data): Docker manages the storage location. Portable. Easy to back up with docker volume commands.

Bind mounts (-v /host/path:/container/path): Maps a specific host directory to a container path. You control the location. Good for development (mount source code) and for accessing host files (SSL certificates, nginx config).

# Named volume (Docker manages location)
volumes:
  - app-data:/data

# Bind mount (specific host path)
volumes:
  - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro

The :ro suffix makes the bind mount read-only inside the container. Nginx reads the config but cannot modify it.

Backing up volumes

# Backup a volume to a tar file
docker run --rm \
  -v app-data:/data \
  -v $(pwd):/backup \
  alpine tar czf /backup/app-data-backup.tar.gz -C /data .

# Restore from a tar file
docker run --rm \
  -v app-data:/data \
  -v $(pwd):/backup \
  alpine tar xzf /backup/app-data-backup.tar.gz -C /data

This creates a temporary Alpine container, mounts the volume and a host directory, and copies the data. Production apps should automate this as a cron job.

Exercises

Exercise 1: Run your app with a named volume for the database. Create some data. Stop and remove the container. Start a new container with the same volume. Verify the data is still there.

Exercise 2: Run your app without a volume. Create data. Remove the container. Start a new one. The data is gone.

Exercise 3: Back up your database volume to a tar file. Restore it to a new volume. Verify the data is intact.

What happens to data inside a container when the container is removed?

← Adding a Reverse Proxy Deploying to a VPS →

© 2026 hectoday. All rights reserved.