Docker Volumes, Networking, and Container Security Basics
Introduction
Containers are disposable, so data, connections, and security need deliberate design. This lesson covers volumes versus bind mounts, user-defined networks, publishing ports without exposing them to the world, and basic hardening including build secrets and image scanning.
Prerequisites
- Docker basics and the Dockerfile lesson
Step-by-Step Guide
Step 1: Persist data with volumes
A container's filesystem disappears when the container is removed. Volumes are managed by Docker and are the preferred way to keep data such as database files. Bind mounts map a specific host folder into the container and suit local development, such as live source code. Mounting over a non-empty container directory hides the files that were there.
docker volume create my-vol
docker volume ls
docker volume inspect my-vol
# volume
docker run -d --name db -v my-vol:/var/lib/data nginx:latest
# bind mount, read-only (explicit --mount syntax)
docker run -d --name web \
--mount type=bind,source="$(pwd)"/site,target=/usr/share/nginx/html,readonly \
nginx:latestStep 2: Connect containers with a user-defined network
On the default bridge network, containers can reach each other only by IP address. On a user-defined bridge network, Docker's built-in DNS lets containers find each other by name, with better isolation. Create a network and attach containers to it.
docker network create my-net
docker run -d --name api --network my-net my-api-image
docker run -it --rm --network my-net busybox ping -c 1 api
docker network ls
docker network inspect my-netStep 3: Publish ports deliberately
Publishing a port with -p makes it reachable from outside the host unless you restrict it. Bind to 127.0.0.1 when only the local machine should connect, such as for a database during development. Ports you do not publish remain reachable only to the host and to containers on the same network.
docker run -d -p 8080:80 nginx # open to the network
docker run -d -p 127.0.0.1:8080:80 nginx # this machine onlyStep 4: Harden the container at runtime
Run as a non-root user, drop capabilities you do not need, and make the filesystem read-only where the app allows it. Start from a restrictive baseline and add back only what is required. Rootless mode, where the Docker daemon and containers run without root privileges, further limits damage from a compromise.
docker run -d --name app \
--user 1000:1000 \
--cap-drop ALL \
--read-only \
--tmpfs /tmp \
my-app-imageStep 5: Keep secrets out of images and scan for vulnerabilities
Anything in a build argument or ENV is stored in image layers. Pass build-time secrets with a secret mount, which is not saved in the image, and read the secret from the mounted file. Then scan your image with Docker Scout to see known vulnerabilities and base image update suggestions.
# Dockerfile
RUN --mount=type=secret,id=npm_token \
NPM_TOKEN=$(cat /run/secrets/npm_token) npm ci
# build with the secret supplied from a file
docker build --secret id=npm_token,src=./npm_token.txt -t myapp:1.0 .
docker scout quickview myapp:1.0FAQs
- Volume or bind mount for a database?
- A volume. It is managed by Docker, portable across hosts, and unaffected by the host's folder layout and permissions. Use bind mounts mainly for development files you want to edit on the host.
- Why can't containers on the default bridge find each other by name?
- Automatic name resolution is only provided on user-defined networks. Create your own network and attach the containers to it, or use Docker Compose, which does this for you.
Summary
Use volumes for persistent data, user-defined networks for name-based connections, and bind published ports to localhost when possible. Run containers as non-root with minimal capabilities, use secret mounts for build secrets, and scan images.
Comments
Log in or create a free account to join the discussion.