Docker & Containers
Learn about containerization with Docker and related technologies.
Why Containers Changed Everything
Before containers, deploying software meant wrestling with environment inconsistencies - "it works on my machine" was a daily frustration. Docker solved this by packaging applications with everything they need to run, making deployments reproducible from a developer laptop to a production cluster. Today, containers are the standard unit of deployment for startups and enterprises alike.
At Wizard Tech Services, our web development projects are containerized by default, ensuring smooth handoffs and consistent environments. We also help teams containerize existing applications and set up orchestration as part of our automation and infrastructure services.
Containers vs. VMs
Virtual machines emulate entire operating systems and take minutes to boot. Containers share the host OS kernel and start in milliseconds, using a fraction of the memory. Use VMs when you need full OS isolation; use containers for application-level packaging and rapid scaling.
Dev-to-Prod Parity
Use Docker Compose locally and Kubernetes in production to keep your environments as similar as possible. The same Dockerfile builds the same image everywhere, eliminating configuration drift and "works on my machine" bugs.
YAML files define your entire application stack in one place. Learn how docker-compose.yml works, essential Compose commands, and see real-world examples including a modded Valheim game server and a Portainer deployment.
Two ways to run Docker - the Desktop GUI application for Mac/Windows or the command-line Engine on Linux. Compare features, resource usage, and learn which approach fits your workflow.
A web-based management UI for Docker - start, stop, and inspect containers from your browser. Deploy stacks from Compose files, manage multiple hosts, and use app templates for one-click deployments.
Health checks, log aggregation, resource metrics, and alerting for your containers. Covers Docker's built-in logging, Prometheus + Grafana stacks, Uptime Kuma, and terminal tools like ctop and Lazydocker.
Containers package your app, runtime, libraries, and config into one portable unit that runs identically anywhere. Covers the container lifecycle, volumes for persistent data, port mapping, and environment variable injection.
Docker images are built in layers via Dockerfile instructions. Write multi-stage builds for smaller images, tag versions, push to Docker Hub or GitHub Container Registry, and scan for vulnerabilities.
Docker Compose & YAML
Define multi-container applications in a single YAML file and bring them up with one command.
YAML (YAML Ain't Markup Language) is a human-readable data format used for configuration files. It uses indentation instead of braces or brackets, making it easy to read and write. Docker Compose uses YAML files to define services, networks, volumes, and environment variables for your containers.
YAML Basics
# Key-value pairs
name: my-app
version: "3.8"
# Lists (use dashes)
ports:
- "8080:80"
- "443:443"
# Nested objects (use indentation - 2 spaces)
environment:
NODE_ENV: production
PORT: "3000"
Important: YAML is indentation-sensitive. Always use spaces (not tabs), and be consistent with 2-space indentation. A single misaligned line will cause a parse error.
Docker Compose reads a docker-compose.yml (or compose.yml) file and creates all the containers, networks, and volumes defined in it. Instead of running multipledocker run commands with long flag lists, you declare everything in one file and bring it all up together.
Essential Commands
$ docker compose up -d # Start all services in background
$ docker compose down # Stop and remove all containers
$ docker compose logs -f # Follow logs from all services
$ docker compose ps # List running services
$ docker compose pull # Pull latest images
$ docker compose restart # Restart all services
$ docker compose up -d --force-recreate # Rebuild and restart
Common YAML Keys
services - define each container (image, ports, volumes, env)
image - Docker Hub image to pull (e.g., nginx:alpine)
build - build from a local Dockerfile instead of pulling
ports - map host:container ports
volumes - persist data or mount host directories
environment - set environment variables
env_file - load variables from a .env file
depends_on - control startup order between services
restart - restart policy (no, always, unless-stopped, on-failure)
networks - attach to custom Docker networks
A real-world compose file we use for hosting a modded Valheim game server with BepInEx, Thunderstore mods, and automatic updates.
version: "3"
services:
valheim:
image: mbround18/valheim:latest
container_name: valheim
stop_signal: SIGINT
ports:
- "2456:2456/udp"
- "2457:2457/udp"
- "2458:2458/udp"
environment:
# --- Basic server settings ---
- PORT=2456
- NAME=My Valheim Server
- WORLD=MyWorld
- PASSWORD=YourPasswordHere
- PUBLIC=1
- TZ=America/New_York
# --- Force stable branch (no public-test) ---
- BETA_BRANCH=
- BETA_BRANCH_PASSWORD=
# --- Auto update (once per day at 04:00) ---
- AUTO_UPDATE=1
- AUTO_UPDATE_SCHEDULE=0 4 * * *
# --- Enable BepInEx framework ---
- TYPE=BepInEx
# --- Mods from Thunderstore (full stack) ---
- |
MODS=
RandyKnapp-EpicLoot-*
Therzie-Warfare-*
Therzie-Wizardry-*
Therzie-Monstrum-*
Azumatt-AzuCraftyBoxes-*
Azumatt-AzuAreaRepair-*
JewelHeim-EpicLoot_Therzie-*
Therzie-Armory-*
ValheimModding-Jotunn-*
ValheimModding-HookGenPatcher-*
ValheimModding-JsonDotNET-*
Therzie-WarfareFireAndIce-*
Therzie-MonstrumDeepNorth-*
Smoothbrain-Guilds-*
Smoothbrain-Groups-*
Advize-PlantEverything-*
ishid4-BetterArchery-*
volumes:
# World saves + characters
- ./saves:/home/steam/.config/unity3d/IronGate/Valheim
# Server install + Odin/BepInEx/mods
- ./server:/home/steam/valheimKey Points
- •
stop_signal: SIGINTensures graceful shutdown and world save - •
TYPE=BepInExenables the mod framework automatically - •
MODSlist pulls from Thunderstore on startup - • Volumes persist world data and mods across container restarts
- • Auto-update cron runs daily at 4 AM
To Run
$ mkdir -p saves server
$ docker compose up -d
$ docker compose logs -f valheim
First startup takes a few minutes to download the server and all mods.
We host and manage game servers just like this - see our Creator & Gaming services.
Deploy Portainer for a web-based Docker management UI - manage containers, images, and networks from your browser.
services:
portainer:
image: portainer/portainer-ce:latest
container_name: portainer
restart: unless-stopped
ports:
- "9443:9443"
- "8000:8000"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- portainer_data:/data
environment:
- TZ=America/New_York
volumes:
portainer_data:
driver: localKey Points
- • Mounts Docker socket for container management
- • Port 9443 serves the HTTPS web UI
- • Named volume persists Portainer settings
- • Access at
https://localhost:9443
To Run
$ docker compose up -d
$ # Open https://localhost:9443
$ # Create admin account on first visit
A typical compose file with a Node.js API, PostgreSQL database, and Nginx reverse proxy.
services:
api:
build: ./api
container_name: app-api
restart: unless-stopped
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgresql://user:pass@db:5432/myapp
- NODE_ENV=production
depends_on:
db:
condition: service_healthy
db:
image: postgres:16-alpine
container_name: app-db
restart: unless-stopped
volumes:
- pgdata:/var/lib/postgresql/data
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=pass
- POSTGRES_DB=myapp
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d myapp"]
interval: 10s
retries: 5
nginx:
image: nginx:alpine
container_name: app-nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- api
volumes:
pgdata:
driver: localWhat This Does
- •
depends_onwith health checks ensures the DB is ready before the API starts - •
build: ./apibuilds from a local Dockerfile instead of pulling an image - • Named volume
pgdatapersists database data across restarts - • Nginx handles SSL termination and reverse-proxies to the API