Documentation Index
Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
Use this file to discover all available pages before exploring further.
Docker Compose
Docker Compose is a tool for defining and running multi-container Docker applications. Think of it as the “recipe card” for your entire application stack: instead of running five separatedocker run commands with various flags you will inevitably forget, you declare everything in one YAML file and bring the whole system up with a single command.
The docker-compose.yml File
Compose uses a YAML file to configure your application’s services.
Complete Example
Real-World Architecture Example
A typical production-like setup with Nginx Reverse Proxy, Backend API, Redis Cache, and PostgreSQL Database. Notice the use of two separate networks —public (internet-facing) and private (internal only). This is a security pattern: the database and cache are unreachable from outside the Docker network, even if someone compromises the reverse proxy.
Key Concepts
Services
Services are the computing units of your application. Each service becomes one or more running containers.build: Build an image from a local Dockerfile. Use this for your own application code.image: Pull and use a pre-built image from a registry. Use this for third-party services (databases, caches, proxies).depends_on: Controls startup order. Withcondition: service_healthy, it waits for the dependency’s healthcheck to pass.
Environment Variables
- Inline: Defined directly in YAML. Fine for non-sensitive values like
NODE_ENV=development. - .env file: Compose automatically reads variables from a
.envfile in the same directory. Use this for secrets and values that change between environments.
Networking
By default, Compose creates a single bridge network for your entire stack. Services can reach each other by service name as the hostname (e.g.,web can connect to http://api:8080). This works because Docker runs an embedded DNS server that maps service names to container IPs. No /etc/hosts hacking required.
Essential Commands
Production vs Development
Overriding Configuration
You can use multiple Compose files to handle different environments.docker-compose.yml: Base config.docker-compose.prod.yml: Production overrides (restart policies, extra volumes).
Key Takeaways
- Use Docker Compose for local development and testing — it replaces a page of
docker runcommands with a single declarative file. - Use
.envfiles to manage secrets and environment-specific configuration. Never commit them to git. - Use
depends_onwith healthchecks to ensure services start in the correct order. Without healthchecks,depends_ononly guarantees start order, not readiness. - Use Named Volumes for database data. Without them, a
docker-compose downdestroys everything. - Use multiple Compose files (
docker-compose.yml+docker-compose.prod.yml) to handle environment differences without duplicating configuration.
Interview Deep-Dive
Your Docker Compose stack has a web service, an API, and a PostgreSQL database. The API crashes on startup with 'connection refused' to the database, even though you have depends_on configured. What is going wrong and how do you fix it?
Your Docker Compose stack has a web service, an API, and a PostgreSQL database. The API crashes on startup with 'connection refused' to the database, even though you have depends_on configured. What is going wrong and how do you fix it?
- The
depends_ondirective without aconditiononly controls container start order — it ensures the database container starts before the API container. But “started” does not mean “ready to accept connections.” PostgreSQL takes several seconds to initialize its data directory, run recovery, and begin listening on port 5432. The API container starts a fraction of a second after the database container and immediately tries to connect, hitting a refused connection. - The fix is to use
depends_onwithcondition: service_healthyand add a healthcheck to the database service. For PostgreSQL, the built-inpg_isreadyutility is the standard check:test: ["CMD-SHELL", "pg_isready -U postgres"]with an interval of 5-10 seconds and 5 retries. - An alternative application-level fix is retry logic with exponential backoff in the API’s database connection code. In production, this is actually the better pattern because orchestrators like Kubernetes do not have a
depends_onequivalent — services must be resilient to dependencies being temporarily unavailable. - The deeper lesson: startup ordering is a development convenience, not a reliability strategy. Production systems should handle dependency failures gracefully at the application level.
Explain the security implications of the 'internal: true' network option in Docker Compose, and describe a real architecture where you would use it.
Explain the security implications of the 'internal: true' network option in Docker Compose, and describe a real architecture where you would use it.
internal: trueon a Docker network means containers on that network have zero route to the host network or the internet. They can only communicate with other containers on the same internal network. There is no NAT, no gateway, no outbound connectivity at all.- The classic use case is a three-tier architecture: a reverse proxy (nginx) on a public network, an API on both public and private networks, and a database on the private internal network only. The database is completely unreachable from outside Docker — even if an attacker compromises the nginx container, they cannot connect directly to the database because nginx is not on the private network.
- This is defense in depth. The API acts as the only bridge between the tiers. If an attacker compromises nginx, they can only reach the API. If they compromise the API, they can reach the database — but they had to chain two exploits to get there, not one.
- In practice, I combine this with read-only volume mounts (
:ro) for configuration files and Docker secrets (mounted as tmpfs) for passwords. The goal is that even in a compromise scenario, the attacker’s lateral movement is maximally constrained.
You run docker-compose down -v by accident on a staging environment with a PostgreSQL database. What just happened, and what is your recovery plan?
You run docker-compose down -v by accident on a staging environment with a PostgreSQL database. What just happened, and what is your recovery plan?
- The
-vflag removes named volumes along with containers and networks. Since the PostgreSQL data was stored in a named volume (db-data:/var/lib/postgresql/data), the entire database is now gone. The data is not recoverable from Docker — once a named volume is removed, the underlying directory in/var/lib/docker/volumes/is deleted. - Immediate recovery depends on your backup strategy. If you have automated
pg_dumpbackups stored externally (S3, a backup server), you restore from the most recent backup. If you used Kafka CDC or logical replication to mirror data elsewhere, you can rebuild from that source. - If there are no backups (worst case), the data is gone. This becomes a lesson in backup hygiene.
- Prevention going forward: First, use a wrapper script or alias that always prompts for confirmation before running
down -v. Second, implement automated database backups on a schedule (dailypg_dumpto S3 at minimum). Third, for critical environments, use an external volume driver (NFS, EBS) where the volume lifecycle is managed outside of Docker Compose. Fourth, consider addingexternal: trueto the volume definition — this tells Compose that the volume is managed externally and refuses to delete it duringdown -v.
external: true on a volume, Compose expects the volume to already exist (created with docker volume create manually). Compose will not create it or destroy it during up and down. This is ideal for databases in shared environments where the data lifecycle should not be tied to the application lifecycle. The trade-off is that your Compose setup is no longer fully self-contained — a new developer running docker-compose up for the first time gets an error unless they create the volume first. For development environments, Compose-managed volumes are more convenient. For staging and production, external volumes are safer.Walk me through how Docker Compose networking works under the hood. What happens when service 'api' resolves the hostname 'db'?
Walk me through how Docker Compose networking works under the hood. What happens when service 'api' resolves the hostname 'db'?
- When Compose creates a custom bridge network (e.g.,
app-net), Docker sets up an embedded DNS server at 127.0.0.11 inside each container. Every container on that network has its/etc/resolv.confpointing to this embedded DNS. - When the API container does a DNS lookup for “db,” the request goes to the embedded DNS server. Docker’s DNS server maintains a mapping of container names (and service names in Compose) to their current IP addresses on that bridge network. It resolves “db” to something like 172.18.0.3.
- Under the hood, each container gets a virtual ethernet pair (veth). One end is inside the container’s network namespace (visible as
eth0), and the other end connects to the bridge interface on the host. Traffic fromapitodbflows through the bridge — the packet goes out ofapi’s veth, through the bridge, and intodb’s veth. - If the database container restarts and gets a new IP, the DNS server updates automatically. This is why custom bridge networks are essential — the default bridge network does not support DNS resolution by container name, so you would have to hard-code IP addresses that change on every restart.
- For cross-host communication (Docker Swarm overlay networks), VXLAN encapsulation wraps the packet in a UDP datagram that travels over the physical network to the other host, where it is de-encapsulated and delivered to the target container.
nslookup db to confirm DNS is resolving. If it resolves, I would ping the IP to check basic connectivity. If ping works, I would nc -zv db 5432 to check TCP connectivity on the database port. If that fails, the database might be listening on a different interface (localhost only instead of 0.0.0.0) — check with docker exec db netstat -tlnp. I would also verify both containers are on the same network with docker network inspect app-net. If they are on different networks, they cannot communicate. Finally, I would check if any iptables rules or the host firewall are interfering.Next: Docker Best Practices →