Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Docker Networking and Volumes Explained

By Kokil Thapa | Last reviewed: August 2026

Getting Docker Networking and Volumes Explained correctly is the difference between a fragile development toy and a resilient production system. Containers are ephemeral by design; without explicit volume configuration, every restart destroys your database records, uploaded media, and application logs. Similarly, default networking exposes services unnecessarily or isolates them so strictly that legitimate internal traffic fails. This guide covers the exact configurations I use for Laravel, WordPress, and custom PHP applications to ensure data safety and secure service communication.

How Do You Configure Docker Networking and Volumes Explained for Data Persistence?

Data persistence is usually the first concept developers need when transitioning from traditional LAMP/LEMP stacks to containers. In my experience shipping Laravel applications and legal-tech portals, relying on container writable layers for storage is a critical failure point. When a container is removed or rebuilt during deployment, that layer vanishes. Named volumes solve this by mapping a directory inside the container to a managed location on the host (typically under /var/lib/docker/volumes/), completely decoupling data from the application lifecycle.

Host Filesystem Persistence ModelDocker Host/var/lib/docker/volumes/app_data/var/lib/docker/volumes/db_dataContainer Runtime/app/storage (Laravel)/var/lib/mysql (Database)Named VolumeNamed Volume
Docker Networking and Volumes Explained: Named volumes map host directories to container paths, ensuring data survives container restarts and rebuilds.

In practice, you should always define top-level volumes in your docker-compose.yml. This makes them inspectable, portable, and independent of any single service definition. For a typical Laravel stack running PHP 8.4 and MySQL 8.4, the configuration looks like this:

<pre><code>services:
  app:
    image: php:8.4-fpm
    volumes:
      - app_storage:/var/www/html/storage
      - ./code:/var/www/html:ro
  db:
    image: mysql:8.4
    volumes:
      - db_data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: secret

volumes:
  app_storage:
    driver: local
  db_data:
    driver: local</code></pre>

A common mistake I see on client projects is using anonymous volumes or bind-mounting host directories for database storage in production. Bind mounts (./data:/var/lib/mysql) inherit host permissions and UID/GID mismatches frequently cause "permission denied" errors after OS upgrades or user changes. Named volumes let Docker manage ownership correctly. On Ubuntu 24.04 servers, this eliminates an entire class of deployment debugging that wastes hours.

When to Use Bind Mounts vs Named Volumes

  • Bind Mounts: Active development only. Useful for hot-reloading code where you edit files locally and expect immediate reflection in the container. Never use for databases or user uploads in production.
  • Named Volumes: All production persistent data. Databases, media uploads, cache directories, SSL certificates. Managed by Docker, backed up via docker run --rm -v db_data:/source -v $(pwd):/backup alpine tar czf /backup/db.tar.gz -C /source ..
  • tmpfs Mounts: Ephemeral sensitive data like session caches or build artifacts that must not touch disk. Stored entirely in RAM.

What Are the Differences Between Bridge, Host, and Overlay Networks?

Understanding network drivers is where Docker Networking and Volumes Explained moves from basic persistence to architectural security. The default bridge network assigns containers random IPs and requires manual port publishing. Custom bridge networks, which I use exclusively, enable automatic DNS resolution: containers reach each other by service name (e.g., db, redis) without hardcoded IPs. This is non-negotiable for microservices or multi-container Laravel deployments where services scale independently.

Network DriverDNS ResolutionIsolation LevelBest Use CaseProduction Ready?
Bridge (Custom)Automatic by service nameFull subnet isolationMulti-container apps, Laravel + MySQL + RedisYes (default choice)
HostN/A (shares host stack)None (uses host ports directly)High-performance networking, legacy apps needing localhostRarely (security risk)
OverlaySwarm-managed DNSCross-node encrypted tunnelDocker Swarm clusters, multi-host orchestrationYes (Swarm only)
MacvlanExternal LAN DNSAppears as physical device on LANLegacy systems requiring direct LAN IP assignmentSituational

For most web projects I deliver, including eCommerce platforms and booking systems, custom bridge networks provide the right balance. They prevent external access to internal services (like databases) while allowing controlled exposure through reverse proxies. Host networking bypasses NAT but exposes every container port directly on the host interface—a security regression I avoid unless benchmarking proves it necessary.

Defining Secure Internal Networks

<pre><code>networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
    internal: true  # No outbound internet access

services:
  nginx:
    networks: [frontend, backend]
    ports: ["80:80", "443:443"]
  app:
    networks: [backend]
  db:
    networks: [backend]</code></pre>

The internal: true flag on the backend network prevents containers from initiating outbound connections. This stops compromised application containers from exfiltrating data or downloading malware. Only the Nginx proxy bridges both networks, acting as the sole ingress point. This pattern aligns with zero-trust principles and simplifies firewall rules on the host.

How Does Container Communication Work in Custom Docker Networks?

Once networks are defined, understanding the actual communication flow prevents debugging nightmares. Docker's embedded DNS server resolves service names to container IPs within the same network. This resolution happens at the kernel level via iptables/nftables rules, not through application-layer proxies. When your Laravel app connects to mysql://db:3306, Docker translates db to the current container IP dynamically—even if the database container restarts and receives a new address.

Container DNS & Packet Flow SequenceApp ContainerDocker DNSDB Container1. Resolve 'db'2. Return 172.18.0.33. TCP Connect 172.18.0.3:33064. Response StreamPort 3306 NOT exposed
Internal DNS resolution enables service discovery without hardcoded IPs. Database ports remain unexposed to the host, accessible only within the custom network.

This DNS-based discovery means you never hardcode IPs in .env files. In my CI/CD pipelines, this allows identical compose files across staging and production—only volume names and secrets differ. A frequent gotcha occurs when developers test connectivity using ping; many minimal container images lack ICMP utilities. Always test with protocol-specific tools: nc -zv db 3306 for TCP, curl http://app:8000/health for HTTP.

Troubleshooting Network Connectivity

  1. Verify network attachment: docker inspect <container> --format '{{json .NetworkSettings.Networks}}' confirms which networks a container belongs to.
  2. Check DNS resolution: docker exec <container> nslookup db should return a valid IP. Failure indicates missing network attachment or typo in service name.
  3. Test port reachability: docker exec <app> nc -zv db 3306 verifies TCP handshake. Timeout suggests firewall rules or incorrect network driver.
  4. Inspect network config: docker network inspect backend shows connected containers, subnet range, and gateway. Mismatched subnets indicate conflicting network definitions.

Why Should You Avoid Default Networks and Anonymous Volumes in Production?

The default bridge network and unnamed volumes are development conveniences that become production liabilities. Default networks lack DNS resolution, forcing IP hardcoding that breaks on restart. Anonymous volumes (created when no name is specified) accumulate orphaned data that consumes disk space silently. On shared hosting or budget VPS instances common in Nepal, this leads to "no space left on device" outages during routine maintenance.

❌ Unsafe PracticesDefault bridge network (no DNS)Anonymous volumes (orphaned data)Bind mounts for DB in prodExposing all ports to hostHardcoded container IPs✅ Production SafeCustom bridge + DNS namesNamed volumes with backup planManaged volumes for all stateInternal networks + reverse proxyService name discovery only
Side-by-side comparison of unsafe defaults versus production-hardened Docker Networking and Volumes Explained configurations.

I enforce strict policies on projects I maintain: every docker-compose.yml must declare explicit networks and named volumes. Code review catches violations before deployment. For teams adopting containers incrementally, start by migrating one service at a time—database first, then cache, then application. This reduces blast radius if misconfiguration occurs. Budget approximately NPR 15,000–25,000 (~USD 110–185) for initial infrastructure audit and migration planning if bringing in external help; preventing data loss pays for itself immediately.

Backup and Restore Strategy for Named Volumes

Volumes are only as safe as your backup routine. Schedule nightly dumps for databases and weekly tars for file storage. Store backups off-host (S3, Backblaze B2, or separate NAS). Test restores quarterly—untested backups are fiction. For Laravel apps, include storage/app/public and storage/logs in volume backups; losing user uploads or audit trails violates compliance requirements in legal-tech contexts I specialize in.

Implementing Docker Networking and Volumes Explained in Real Deployment Pipelines

Theory matters less than execution. In production deployments using Deployer 7 or GitLab CI, volume and network definitions live in version-controlled compose files alongside application code. Secrets (database passwords, API keys) inject via Docker secrets or environment files excluded from git. During zero-downtime deploys, new containers attach to existing networks and volumes before old ones detach—ensuring continuous availability.

A pattern I've seen repeatedly succeed: separate compose files for dev, staging, and production sharing a common base via extends or override files (docker-compose.prod.yml). Dev uses bind mounts for code sync; prod uses named volumes and read-only code mounts. Networks stay consistent across environments to prevent "works locally" failures. When upgrading MySQL 8.0 to 8.4, snapshot the volume first, test migration in isolated network, then swap. Rollback is instant: restore volume, revert compose tag.

For teams managing multiple sites on shared infrastructure (like the sister sites I maintain on EC2), prefix volume and network names with project identifiers (notary_db_data, notary_backend). This prevents accidental cross-contamination during cleanup scripts. Document naming conventions in repository READMEs; future maintainers will thank you.

Securing Your Stack With Proper Docker Networking and Volumes Explained Configuration

Security isn't optional—it's architectural. Proper Docker Networking and Volumes Explained implementation reduces attack surface significantly. Internal networks prevent lateral movement. Read-only root filesystems (read_only: true) combined with tmpfs for temporary dirs stop malware persistence. Volume encryption (via LUKS on host or encrypted drivers) protects data at rest. Regularly prune unused volumes (docker volume prune) to eliminate forgotten data residues.

If you're building business-critical systems and need hands-on guidance implementing these patterns, reach out to discuss your infrastructure needs. Whether migrating legacy PHP apps to containers or designing greenfield architectures, getting networking and volumes right from day one prevents costly rework later. The configurations outlined here reflect battle-tested approaches from over 15 years of shipping production web systems—they work because they prioritize reliability over novelty.

Frequently Asked Questions

Bridge creates an isolated internal network where containers communicate via IP; host mode shares the host's network stack directly, removing isolation but improving performance.

Use named volumes or bind mounts to store data outside the container lifecycle, ensuring files survive restarts, rebuilds, and deployments without loss.

Bind mounts for active development syncing local code; named volumes for production databases and persistent application state managed entirely by Docker.

Containers on different networks cannot reach each other by default. In my experience debugging Laravel microservices, this usually happens when services are defined in separate compose files without an explicit external network declaration. Connect them using docker network connect or define a shared network in your docker-compose.yml. Always verify connectivity with docker exec ping rather than assuming DNS resolution works across network boundaries automatically.

Docker provides automatic DNS resolution for containers on the same user-defined bridge network using service names as hostnames. This fails silently on the default bridge network, which requires legacy links. On production deployments I manage, I always create custom bridge networks because they support automatic service discovery and better isolation. If name resolution fails, check that both containers share the exact same network and that you are not accidentally relying on the deprecated default bridge behavior.

Host networking bypasses network isolation entirely, exposing all host ports and services to the container. A compromised container gains direct access to host interfaces, localhost services, and potentially sensitive metadata endpoints. I avoid host mode in production except for specific high-performance proxies or monitoring agents where the trade-off is justified. For standard web applications and databases, custom bridge networks with explicit port mapping provide necessary isolation while maintaining adequate performance for typical PHP and Node.js workloads serving real traffic.

Define a named volume in your compose file and mount it to /var/lib/mysql inside the container. Never rely on container filesystem layers for database storage. On eCommerce projects like Nepal Gift Card, I use named volumes with explicit driver options for backup compatibility. Set proper ownership with chown mysql:mysql in an entrypoint script if permissions fail after volume creation. Always test restore procedures before going live, as volume corruption or misconfiguration during deployment causes silent data loss that only surfaces during recovery attempts.

Yes, but the method depends on your OS and network mode. On Linux with bridge networking, use host.docker.internal or the host gateway IP 172.17.0.1. On macOS and Windows Docker Desktop, host.docker.internal resolves automatically. For production Linux servers I administer, I prefer adding extra_hosts in docker-compose.yml for explicit mapping rather than relying on implicit gateway addresses. Avoid exposing host services to all containers; restrict access through firewall rules and network segmentation to limit blast radius if a container is compromised.

Permission issues occur when the container process UID differs from the volume owner. Fix this by setting user/group IDs in your Dockerfile or compose file to match the host directory owner. For bind mounts in development, run chown -R on the host path. Named volumes may need initialization scripts that adjust permissions on first run. I have encountered this repeatedly with Laravel storage directories and WordPress uploads on Ubuntu servers. Always verify mounted paths with docker inspect and test write access immediately after container startup rather than discovering failures during runtime operations.

Custom bridge networks suit most single-host production deployments running Laravel, WordPress, or Node.js applications. They provide DNS resolution, isolation, and configurable subnets without overlay overhead. Overlay networks are necessary only for multi-host Swarm or Kubernetes clusters. In my deployment workflows using Deployer 7 on EC2 instances, I stick with bridge networks for simplicity and predictable performance. Avoid the default bridge entirely as it lacks DNS and requires deprecated link syntax. Reserve macvlan or ipvlan only when containers must appear as distinct physical devices on your LAN.

Stop the writing container or flush buffers before backing up to ensure consistency. Use docker run with a temporary container mounting both the target volume and a backup destination, then tar the contents. For MySQL and PostgreSQL, always use logical dumps via mysqldump or pg_dump instead of raw file copies to avoid corrupting transaction logs. On client projects, I schedule automated backups via cron on the host, storing archives outside the Docker root directory. Test restores quarterly, as untested backups provide false confidence and frequently fail during actual disaster recovery scenarios.

Data written to container filesystem layers is ephemeral and destroyed when the container is removed or recreated. You likely forgot to mount a volume to the correct path or used an anonymous volume that got orphaned. Verify mounts with docker inspect and confirm the destination path matches your application expectations. I have seen this issue with Redis caches and Laravel session stores where developers assumed persistence was automatic. Always declare volumes explicitly in docker-compose.yml and document which paths require persistence so future maintainers understand the storage contract.

Create separate user-defined bridge networks for different trust zones and attach containers only to networks they need. Containers cannot communicate across networks unless explicitly connected. For a legal-tech portal handling sensitive documents, I isolate the database on a private backend network while the web container bridges frontend and backend networks. Use iptables or nftables on the host for additional filtering if required. Default deny policies combined with explicit allow rules prevent lateral movement if one service is compromised, following the same principle applied to traditional server hardening.

Alpine uses musl libc which handles DNS differently than glibc-based images, often failing with certain resolver configurations or search domains. This manifests as intermittent connection timeouts in PHP or Node applications. Switch to a glibc-based image like debian or ubuntu for production reliability, or install bind-tools and configure /etc/resolv.conf explicitly. I have debugged this on multiple API integration projects where HTTP clients failed unpredictably. Testing DNS resolution with nslookup inside the container during CI catches these issues before deployment rather than discovering them through cryptic production error logs.

Bridge networking adds minimal latency, typically under 0.1ms for container-to-container communication on modern hardware. Host networking eliminates this overhead but sacrifices isolation. For PHP-FPM and Nginx setups I deploy, bridge performance is indistinguishable from bare metal for typical web request patterns. Bottlenecks usually stem from application logic, database queries, or external APIs rather than network drivers. Only benchmark and consider host mode if profiling confirms networking as the actual constraint. Premature optimization here introduces security debt without measurable user-facing benefit for most business applications.

Share this article

Quick Contact Options
Choose how you want to connect me: