Container Security: Image Scanning, Hardening, and Runtime Protection
Containers Are Not Sandboxes by Default
There is a persistent misconception that containers provide inherent security isolation. They do not — at least not in the way most people assume. Containers share the host kernel, and a misconfigured container running as root with host network access is functionally equivalent to a process running directly on the host. The security benefits of containers come from deliberate hardening, not from the container abstraction itself.
This guide covers the practical security measures for containerized hosting deployments: image scanning for known vulnerabilities, building minimal hardened images, running containers without root privileges, and monitoring container behavior at runtime to detect compromise.
Image Scanning: Know What You Are Deploying
Every container image is a stack of filesystem layers, and each layer contains software packages with their own vulnerability histories. A base image pulled from a public registry may contain dozens of known CVEs on the day you pull it. Image scanning identifies these vulnerabilities before they reach production.
How Scanning Works
Image scanners decompose the container image into its constituent packages — OS packages (apt, apk, yum) and application dependencies (npm, pip, gem, Maven). Each package version is checked against vulnerability databases (CVE, NVD, distribution-specific advisories). The scanner reports which packages have known vulnerabilities, the severity of each, and whether a fixed version is available.
When to Scan
- During the build: Integrate scanning into your CI/CD pipeline. If the build produces an image with critical vulnerabilities, fail the pipeline. This prevents vulnerable images from being pushed to your registry.
- In the registry: Scan images stored in your container registry on a schedule (daily). Vulnerabilities are discovered continuously — an image that was clean last week may have newly disclosed CVEs today.
- Before deployment: Add an admission control step that blocks deployment of images with unresolved critical vulnerabilities. This is your last line of defense before a vulnerable image runs in production.
Practical Tooling
Trivy, Grype, and Snyk are widely used open-source and commercial scanners. Most container registries (Harbor, GitLab Container Registry, cloud-provider registries) offer built-in scanning capabilities. The tools are mature and straightforward to integrate — the barrier is not tooling but process discipline.
Image Hardening: Build Minimal Images
The best way to reduce your vulnerability surface is to reduce what is in the image. Every package you include is a potential vulnerability. Every tool you install is a potential attack vector.
Use Minimal Base Images
Start with the smallest base image that supports your application. Alpine Linux images are a fraction of the size of Ubuntu or Debian images. Distroless images go further — they contain only the application runtime and its dependencies, with no shell, no package manager, and no utilities. An attacker who gains code execution inside a distroless container has very few tools available to escalate the attack.
Multi-Stage Builds
Use multi-stage Docker builds to separate build-time dependencies from runtime dependencies. The build stage includes compilers, build tools, and development libraries. The final stage copies only the compiled application and its runtime dependencies into a clean, minimal image. Build tools never reach production.
Pin Dependencies
Pin your base image to a specific digest (not just a tag) and pin all package versions. Tags are mutable — node:20-alpine today may be a different image tomorrow. Digests are immutable. Pinning ensures reproducible builds and prevents silent changes in your dependency chain.
Remove Unnecessary Capabilities
Default container images often include more than they need: setuid binaries, cron, SSH clients, and network tools. Remove or disable anything the application does not require. The fewer binaries in the image, the smaller the attack surface.
Runtime Security: Rootless Containers and Beyond
Never Run as Root
Running a container process as root means that a container escape vulnerability gives the attacker root access to the host. Always specify a non-root user in your Dockerfile with the USER instruction. Configure file permissions so the application can read and write what it needs without root privileges.
Drop Linux Capabilities
By default, Docker containers run with a subset of Linux capabilities. Drop all capabilities and add back only the specific ones your application needs. Most web applications need zero Linux capabilities. Database containers may need CHOWN and SETUID during initialization but not during normal operation.
Read-Only Filesystems
Mount the container's root filesystem as read-only. The application writes to explicitly defined volumes for data and temporary files. A read-only filesystem prevents an attacker from modifying application binaries, installing tools, or dropping persistent backdoors inside the container.
Seccomp and AppArmor Profiles
Seccomp profiles restrict the system calls a container can make. AppArmor and SELinux profiles restrict file access, network access, and capabilities at the kernel level. Docker and Kubernetes apply default profiles, but custom profiles tailored to your application provide tighter restrictions. If your application never uses ptrace, block it. If it never creates raw sockets, block it.
Runtime Monitoring and Detection
Prevention alone is insufficient. You need to detect when something unexpected happens inside a running container:
- Process monitoring: Your web application container should run one process. If a new process spawns — a shell, a cryptocurrency miner, a network scanner — that is a strong indicator of compromise. Runtime security tools like Falco monitor system calls and alert on unexpected process creation.
- Network monitoring: Containers should communicate only with expected endpoints. An outbound connection to an unknown IP address, especially on unusual ports, warrants immediate investigation.
- File integrity: With a read-only filesystem, any modification to application binaries is impossible. For containers with writable volumes, monitor for unexpected file changes in sensitive directories.
- Drift detection: Compare the running container against its original image. Any difference — new files, modified binaries, additional packages — indicates drift that may be malicious.
Supply Chain Considerations
Your container images are only as secure as the images and packages they are built from:
- Use trusted base images: Pull from official repositories. Verify image signatures where available. Avoid random community images with no maintenance history.
- Scan third-party images: Even official images may contain vulnerabilities. Scan everything, regardless of the source.
- Private registries: Host your own container registry with access controls, vulnerability scanning, and retention policies. Do not deploy directly from public registries to production.
- Image signing: Sign your images in the CI/CD pipeline and verify signatures before deployment. This ensures that only images built by your pipeline — not tampered copies — can be deployed.
A Container Security Checklist
- Scan images in CI/CD and block builds with critical vulnerabilities
- Use minimal base images (Alpine, distroless)
- Multi-stage builds to exclude build-time dependencies
- Pin base images to digests, pin package versions
- Run as non-root user
- Drop all Linux capabilities, add back only what is needed
- Mount root filesystem as read-only
- Apply seccomp and AppArmor profiles
- Monitor runtime for unexpected processes, network connections, and file changes
- Sign images and verify signatures before deployment
- Use a private registry with access controls and scheduled rescanning
The Bottom Line
Container security is not a single configuration — it is a layered practice spanning the build, the registry, the deployment, and the runtime. Scan images to catch known vulnerabilities. Harden images to minimize the attack surface. Run with least privilege to limit the blast radius. Monitor at runtime to detect what prevention missed. Each layer reduces risk independently, and together they create a defense-in-depth posture that makes containerized deployments genuinely more secure than the alternative.