Containers have become the default way many small development teams package and deploy applications, and Docker specifically remains the most widely used tool for building and running them. Container security is often treated as an afterthought compared to the broader cloud infrastructure concerns covered in our cloud security basics guide, but a misconfigured container adds its own distinct set of risks worth addressing specifically.
Do not run containers as root
By default, many container images run their main process as the root user inside the container, which is convenient during development but represents unnecessary risk in production. If an attacker manages to exploit a vulnerability in the containerized application, running as root inside the container makes it meaningfully easier to escalate that initial foothold into broader access, including in some documented scenarios escaping the container itself and affecting the underlying host system.
Configure your Dockerfile to create and switch to a non-root user for the application’s actual runtime process. This is a small, one-time change to your Dockerfile that meaningfully reduces the potential impact of a container-level compromise, and is one of the most commonly skipped basic container security practices we see in small team environments.

Scan images for known vulnerabilities
Container images are typically built from a base image (an official Node.js, Python, or Linux distribution image, for example) plus your own application code and dependencies layered on top. Vulnerabilities in the base image or in your dependencies are discovered continuously, and a scanning tool run against your built images can identify known vulnerabilities before they reach production.
Several free and low-cost scanning tools (Docker Scout, Trivy, and Snyk’s free tier among them) integrate into a typical build pipeline, scanning images automatically as part of your CI/CD process rather than requiring manual scans run inconsistently. Set a policy for how your team responds to findings — critical vulnerabilities blocking a deployment until addressed, lower-severity findings tracked and scheduled for a fix — rather than running the scan without a clear process for acting on what it finds.
Keep base images updated
A container built from an outdated base image carries forward every vulnerability that has been discovered and patched in newer versions of that base image since it was built. Rebuilding and redeploying containers periodically, even without any application code changes, ensures your running containers benefit from upstream security patches rather than running indefinitely on a base image snapshot from months or years earlier.
Automating this — a scheduled rebuild and redeploy even when application code has not changed, specifically to pick up base image updates — is more reliable than depending on someone remembering to manually trigger rebuilds periodically, which in practice tends to happen inconsistently once the initial deployment is stable and no longer receiving active development attention.
Avoid storing secrets in images or Dockerfiles
Hardcoding an API key, database password, or other secret directly into a Dockerfile or baking it into an image at build time means that secret persists in the image’s layers, potentially retrievable by anyone with access to the image even after the value is later changed in your actual running configuration. This is a container-specific variant of the credentials-in-code problem covered in our cloud security basics guide, and the fix is similar: use environment variables injected at runtime, or a dedicated secrets management tool, rather than baking secrets into the image itself.
If a secret has already been baked into an existing image, rotating that secret is necessary even after switching to a better practice going forward, since the old image (and any registry storing it) may still contain the exposed value in its history.
Limit container resource access and capabilities
Docker containers can be run with elevated privileges or expanded system capabilities beyond what most applications actually need, generally enabled during development to work around a specific issue and never revisited for production. Explicitly drop unnecessary Linux capabilities and avoid running containers in privileged mode unless there is a specific, well-understood reason requiring it, which is uncommon for typical web application workloads.
Similarly, set explicit resource limits (memory and CPU) on containers, which protects against both a misbehaving container consuming resources needed by other containers on the same host, and certain denial-of-service scenarios where an attacker attempts to exhaust available resources through a compromised or vulnerable container.
Securing your container registry
The registry storing your built container images deserves the same access control scrutiny as any other sensitive infrastructure component, since it contains your actual application code and, if not properly configured, potentially secrets baked into image layers as discussed above. Ensure your registry requires authentication for both pushing and pulling images (a surprising number of registries are left with public read access by default or through misconfiguration) and apply the same IAM least-privilege principles covered in our IAM guide to who can push new images versus who can only pull existing ones.
Network segmentation between containers
By default, containers on the same Docker network can often communicate with each other more freely than is actually necessary for the application to function correctly. Define explicit network policies restricting which containers can communicate with which others, rather than allowing broad default connectivity — a compromised container should ideally not have unrestricted network access to every other container in your environment, particularly ones handling more sensitive data or functions.
Read-only filesystems where possible
Many containerized applications do not actually need to write to their own filesystem during normal operation — configuration is typically supplied through environment variables, and application data is usually stored in an external database or storage service rather than the container’s local filesystem. Running a container’s filesystem as read-only, where the application’s actual behavior allows it, removes an entire category of potential attack — malware or an attacker attempting to write malicious files to the container’s filesystem simply cannot, since the filesystem itself rejects the write.
This is not appropriate for every workload — some applications genuinely need to write temporary files during operation — but for stateless web applications and APIs, which represent a large share of typical small-team container workloads, a read-only filesystem is often achievable with minimal application changes, usually just explicitly defining a small writable temporary directory for anything that does need to write.
Practical starting checklist
1. Update Dockerfiles to run application processes as a non-root user.
2. Add automated vulnerability scanning to your build pipeline with a clear response policy for findings.
3. Schedule periodic rebuilds to pick up base image security patches, even without application code changes.
4. Remove any secrets baked into existing images and switch to runtime environment variables or a secrets manager going forward.
5. Review and drop unnecessary container capabilities and privileged mode usage.
6. Confirm your container registry requires authentication and apply least-privilege access to it.
Frequently asked questions
Do these practices apply to Kubernetes as well as plain Docker?
Yes, the underlying principles apply equally — Kubernetes adds its own additional layer of configuration (pod security policies, network policies at the cluster level) on top of the same container-level fundamentals covered here, rather than replacing the need for them.
How often should we actually rebuild containers to pick up base image updates?
Monthly is a reasonable default for most small teams, balanced against the operational overhead of frequent rebuilds and redeployments. For applications handling particularly sensitive data, more frequent rebuilds may be justified given the higher stakes of running on an outdated base image longer than necessary.
Is a free vulnerability scanning tool good enough, or do we need a paid option?
Free tiers of tools like Trivy and Docker Scout provide meaningful coverage for most small teams’ needs. Paid options typically add more advanced features like policy enforcement integration and broader compliance reporting, which become more relevant as a team scales rather than being essential from day one.
What is the single highest-impact container security practice for a team just getting started?
Running containers as a non-root user and adding automated vulnerability scanning to the build pipeline are both relatively low-effort changes with meaningful security benefit, making them a reasonable starting point before working through the more involved practices like network segmentation and capability restrictions.
Should we scan third-party images we did not build ourselves before using them?
Yes — any image pulled from a public registry, even an apparently official one, should be scanned before use in production, and preferably pulled from a verified publisher rather than an unverified community upload. Treat third-party images with at least the same scrutiny as third-party code dependencies in your application itself.
For more on securing your broader cloud infrastructure, see our cloud security basics guide and full hardening guides section.