Docker makes deployment reproducible and portable, but it also introduces security concerns that many web developers overlook. A misconfigured container can expose your host system, leak secrets, or give attackers a persistent foothold. Here are the essential security practices every developer should follow when containerizing web applications.
Start with the smallest image possible. Instead of node:20 (which includes a full Debian installation), use node:20-alpine or even a distroless image. Fewer packages mean fewer vulnerabilities. A typical node:20 image has over 400 known CVEs at any given time; the Alpine variant typically has under 10. Multi-stage builds let you install build tools in one stage and copy only the compiled output to a minimal runtime image.
By default, Docker containers run as root. If an attacker breaks out of your application, they're root inside the container — and with certain misconfigurations, root on the host. Add a USERinstruction to your Dockerfile: create a non-root user with addgroup andadduser, then switch to it. Set filesystem permissions explicitly so your app can only read and write what it needs.
Never bake secrets (API keys, database passwords, tokens) into your Docker image. Anyone who pulls the image can extract them. Use environment variables injected at runtime, Docker secrets (in Swarm), or a vault service. Add a .dockerignore file that excludes .env,.git, and node_modules to prevent accidental inclusion of sensitive files in the build context.
Use docker scout, Trivy, or Snyk to scan your images for known vulnerabilities before deploying. Integrate scanning into your CI/CD pipeline so that every push is checked automatically. Set a policy: no critical or high-severity CVEs in production images. Pin your base image versions to avoid surprise upgrades that introduce new vulnerabilities.
Don't expose ports you don't need. Use Docker networks to isolate services — your database container should never be reachable from the public internet. Only the reverse proxy or API gateway should have external port mappings. Use read_only: true in your compose file to mount the container filesystem as read-only, preventing attackers from writing malicious files even if they gain shell access.