A network-facing image processor has a parsing bug. An attacker turns one malformed upload into arbitrary code execution inside the container.
At that point, the word “containerized” does not answer the important questions. Which user is the process running as? Which host paths are mounted? Which Linux capabilities remain? Which system calls are allowed? Can the process reach internal services? Does container root map to host root?
Container security is the work of limiting the answers.
Assume the application can be compromised. Then minimize the authority, kernel interface, data, and network reach available to the compromised process.
No single setting creates a complete sandbox. Useful containment comes from several independent controls that all reduce what one process can do.
Security configuration should answer a concrete threat rather than copy a generic hardening list.
For a backend container, useful starting scenarios include:
Remote application compromise. Untrusted input gives an attacker control of the service process. The goal is to keep that process from changing the host, reading unrelated secrets, or reaching sensitive systems.
Malicious or compromised image. Code starts hostile rather than becoming hostile later. Startup scripts, dependencies, and bundled tools are part of the workload being constrained.
Neighboring workload compromise. Another container on the same host becomes hostile. The goal is to prevent it from observing or modifying this container and to keep shared resources available.
Configuration error. An operator accidentally exposes a host directory, a control socket, a capability, or a network path. Good defaults should make dangerous access explicit rather than incidental.
The trust relationship affects the required boundary. Containers for components owned by one team may be an appropriate isolation layer. Running mutually hostile customer workloads may justify a sandboxed runtime, a lightweight virtual machine, a full VM, or separate nodes in addition to container controls.
A container process executes ordinary Linux system calls against the host kernel.
Namespaces can give the processes different views, and access controls can reject operations, but there is only one kernel enforcing those decisions.
This has two consequences.
First, a kernel vulnerability reachable through an allowed interface may cross boundaries that correct namespace configuration was supposed to enforce. Kernel and runtime patching are therefore part of container security.
Second, reducing the reachable kernel interface matters. A service that cannot request unnecessary mount, tracing, module, device, or namespace operations presents fewer opportunities for misuse and fewer kernel paths to attack.
A virtual machine adds a guest-kernel and hypervisor boundary, but “VM” does not automatically mean secure and “container” does not automatically mean insecure. The correct choice depends on the adversary, the exposed interfaces, the quality of each implementation, and the cost of a boundary failure.
Namespaces answer questions such as which processes, mounts, network interfaces, IPC objects, and hostnames a task can see.
That is necessary isolation, but it is not sufficient authorization.
A mount namespace can hide the host root filesystem, yet a bind mount can deliberately expose /srv/payments inside it. A network namespace can provide a separate interface and routing table, yet the runtime can connect that namespace to the production network. A PID namespace can hide host process identifiers, yet a broadly privileged process may still reach dangerous kernel operations.
Namespace sharing removes specific isolation boundaries. Options that join the host's PID, network, or IPC namespace should therefore be treated as security decisions, not performance conveniences.
Inspect namespace identities for a running process from the host:
Two processes that print the same namespace identifier share that namespace object. Different identifiers show separation for that namespace type; they do not prove that every other control is safe.
UID 0 is evaluated in a user-namespace context.
Without user-namespace remapping, container UID 0 can also be UID 0 in the host's initial user namespace. Namespaces, removed capabilities, seccomp, mount policy, and mandatory access control can still restrict it, but the identity becomes dangerous if another boundary is misconfigured or escaped.
With a user namespace, UID 0 inside can map to an unprivileged host UID:
The process can hold capabilities over resources governed by its child user namespace without holding equivalent capabilities over the host's initial user namespace.
Inspect mappings from the host using the container process's host PID:
An entry similar to:
maps container UID 0 to host UID 100000.
User namespaces substantially reduce the host authority represented by container root. They do not make every kernel interface safe, remove application permissions, or protect a host file that has been deliberately made accessible to the mapped UID.
A traditional rootful container engine uses a privileged host daemon or privileged helpers to create namespaces, mounts, networks, cgroups, and device access. A flaw or exposed control API in that component can therefore carry host-level consequences.
A rootless runtime runs the engine and container processes under an unprivileged host user, using user namespaces and other kernel facilities to create the container environment. This reduces the host authority held by the runtime itself as well as the identity represented by container root.
Rootless operation can impose limitations on device access, networking, privileged ports, cgroup management, and some mount types, depending on the kernel and host configuration. Those constraints are often desirable for ordinary application workloads but may be incompatible with host-management containers.
Rootless does not remove the shared kernel, fix application vulnerabilities, or make exposed files safe. It changes the consequence of a runtime or container compromise by removing host privilege that the deployment did not require.
Even when user-namespace mapping is enabled, running the service itself as a non-root container user adds another layer.
For example, an Alpine-based image can establish a dedicated identity:
The image must also grant that identity access to exactly the files and directories the service needs. Setting USER without fixing ownership often produces startup failures that teams “solve” by returning to root.
A high, explicit numeric UID reduces accidental overlap with common host system identities in environments without user-namespace remapping, but bind-mount ownership must still be checked. Numeric identity also avoids depending on user-name lookup when the runtime launches the process.
Running as non-root does not make compromise harmless. The process can still read its configuration, use inherited file descriptors, access mounted data permitted to that UID, and make network requests allowed by policy. It limits damage by removing authority the service never needed.
Linux capabilities divide many traditional root checks into named categories.
A container runtime can remove capabilities from the process's permitted, effective, and bounding sets. A strong starting point is to drop all capabilities and add back only a demonstrated requirement:
If an application truly needs one capability, grant that one. For example, on a system where binding a low-numbered port requires CAP_NET_BIND_SERVICE:
Many backend services listening on an unprivileged port need no capabilities at all.
Some capability names sound narrower than their practical reach. CAP_SYS_ADMIN gates many unrelated administrative operations and should not be used as a generic fix for EPERM. CAP_DAC_OVERRIDE, CAP_NET_ADMIN, CAP_SYS_PTRACE, and device-related authority can also greatly increase consequences after compromise.
Capabilities are not a complete sandbox. A process with an empty capability set retains ordinary access granted by its UID, groups, open descriptors, mounts, and security labels.
Do not rely only on an image manifest or deployment file. Inspect the running process:
The capability fields are hexadecimal bitmasks. If capsh is installed on the host, decode one:
CapEff describes capabilities currently active for checks. CapPrm describes capabilities the thread may make effective. CapBnd is a ceiling on capability acquisition through executable-file metadata. CapAmb can carry selected capabilities across execution of ordinary programs.
Checking only CapEff can miss authority that a process can reactivate or pass through an execution path. Verify the complete intended state.
Because Linux capability state is technically per-thread, a deeply audited multithreaded service may also need inspection under /proc/PID/task/TID/status. Container runtimes normally establish a consistent initial credential state before starting the application.
execLinux's no_new_privs flag prevents a process and its descendants from gaining additional privilege through execve().
This blocks privilege increases that would otherwise come from set-user-ID, set-group-ID, or file-capability executable metadata. It does not remove authority the process already holds.
Enable it through the runtime:
Verify it:
NoNewPrivs: 1 confirms the restriction.
This is especially valuable when an image contains utilities the main service does not control. If compromised code executes one of them, privileged executable metadata cannot increase the descendant's authority.
Seccomp filter mode evaluates system calls before their normal kernel implementations run. A container runtime can install a policy as it starts the process, so the application does not need seccomp-specific code.
A practical profile allows the calls needed by the workload and rejects unnecessary or unusually dangerous interfaces. If compromised code attempts a blocked operation, the policy can return an error, log the event, send SIGSYS, or terminate the task depending on the configured action.
Inspect the running state:
For Seccomp, value 0 means disabled, 1 means strict mode, and 2 means filter mode. Seccomp_filters, when provided by the kernel, reports attached filters.
Seccomp filters operations, not pathnames or business objects. Allowing openat() does not grant access to every file; file permissions and mandatory policies still decide which object may be opened. Conversely, denying mount() does not prevent data theft through an already open socket.
Disabling seccomp globally because one application receives EPERM discards containment for every other syscall. Identify the rejected call, determine whether it is legitimate, and adjust the smallest applicable policy.
Linux security modules add policy checks beyond ordinary owner, group, mode, and capability decisions.
SELinux commonly uses security labels and type-enforcement policy. AppArmor commonly confines programs using profiles centered on executable identity and path-oriented rules. The exact policy language and host setup differ, but both can restrict which files, capabilities, signals, and other operations a container process may use.
This complements seccomp:
A call must pass all applicable layers.
Inspect a process's current security attributes where the host exposes them:
When an operation fails, check the host's audit or security-module logs before weakening policy. A denial often identifies the subject, target, and attempted operation needed to create a narrow rule.
Running a workload “unconfined” may make an error disappear, but it also removes independent enforcement that is valuable precisely when ordinary permissions or application behavior fail.
Image layers are immutable, but a normal running container usually has a writable upper layer. Compromised code can use that layer to replace application files, alter configuration, download tools, or leave persistence for the remainder of the container's life.
A read-only root filesystem removes that broad write target:
Applications often need a few writable locations. Supply them explicitly:
This design turns unexpected writes into visible failures and makes writable state easy to enumerate.
Read-only does not mean confidential. The process can still read any file allowed by its credentials and mounts. It also does not make explicitly attached volumes read-only; each mount has its own flags.
Where data only needs to be consumed, mount it read-only:
Keep host mounts narrow. Mounting the host root, sensitive /proc or /sys views, device nodes, or runtime control sockets can defeat much of the surrounding isolation.
A Unix socket is not harmless because it appears as a file.
Mounting a container-engine control socket into a workload gives that workload access to a high-authority API. Depending on the daemon and its permissions, the client may be able to create privileged containers, mount host paths, or otherwise gain host control.
The same principle applies to device nodes. A device interface can expose raw storage, accelerators, kernel drivers, or host-management functions. File permissions, device policy, capabilities, and security modules all participate in controlling access.
Grant only the specific device and operations a workload requires. Avoid broad “all devices” access. Code that parses untrusted data should not also have direct access to powerful device interfaces unless the architecture genuinely requires it.
A new network namespace starts with a separate network stack. The runtime normally connects it to other networks using virtual interfaces, routing, and filtering.
Connectivity is therefore policy, not an automatic property of the namespace.
An application may need inbound traffic from a gateway but no direct access to databases outside its service scope. A batch converter may need no external network connectivity:
For connected services, restrict both ingress and egress using the networking facilities available on the host or orchestrator. Egress matters because compromised code can use outbound connections to exfiltrate data, download tooling, scan internal services, or contact an attacker's control system.
Network policy does not replace application authentication. A permitted client can still make unauthorized business requests unless the service verifies identity and authorization itself.
Availability is a security property. A compromised or defective container should not be able to consume every host task slot, byte of memory, or unit of CPU time.
Cgroup limits can contain:
pids.maxmemory.maxThese limits reduce denial-of-service impact on neighboring workloads. They do not prevent the process from misusing the resources it is allowed to consume.
Limits also need operational headroom. A memory boundary set below normal demand makes the service easy to kill, and an extremely small PID limit can prevent health checks or shutdown helpers from starting. Security policy must remain compatible with correct lifecycle behavior.
--privileged Changes the Threat ModelPrivileged container modes are intended for unusual workloads that must manage host-like resources.
In common runtimes, privileged mode grants a broad capability set, expands device access, and relaxes standard seccomp or mandatory-access-control confinement. The exact changes are runtime-specific, but the result is much closer to host administrative authority than an ordinary container receives.
This should not be used as a troubleshooting shortcut:
If an application fails with “Operation not permitted,” identify which check denied it:
Then grant the narrowest requirement. --privileged can hide the original cause while greatly increasing the chance that an application compromise crosses into the host.
Runtime confinement cannot make untrusted image contents trustworthy.
Use small images containing the application and required runtime files rather than compilers, package managers, shells, and debugging tools that production does not need. Fewer components reduce patching work and remove convenient tools an attacker might reuse, although their absence does not prevent an attacker from implementing or downloading equivalents when network and write access permit it.
Pin and verify the image artifact deployed to production. A mutable tag can refer to different content over time, while a content digest identifies exact bytes.
Do not bake secrets into an image layer. Removing a secret in a later layer does not erase it from the earlier immutable layer. Supply secrets at runtime through a mechanism that limits which workload can read them, keep file permissions narrow, and avoid exposing them in logs or command-line arguments.
Environment variables are convenient but can appear in runtime inspection, diagnostic output, or process environments accessible to authorized observers. Prefer a dedicated secret-delivery mechanism when the platform provides one, and ensure the application does not copy the value into logs or crash reports.
Image scanning can identify known vulnerable packages and obvious secret leaks. It cannot prove that the application is free of logic bugs or that runtime permissions are safe.
Consider an offline report worker that needs read-only input, a writable output volume, and a small temporary directory. It does not need external network access or Linux capabilities. Assume the output volume has been prepared so the effective mapped identity for container UID 10001 can write to it.
An illustrative launch is:
The readable tag keeps the example compact. A production deployment should resolve and record the exact approved image digest.
Each setting answers a separate question:
--user removes container-root identity from the application.--read-only prevents mutation of the root filesystem.--cap-drop=ALL removes capability-based administrative authority.no-new-privileges prevents later execution from gaining privilege.--network none removes external connectivity the offline job does not need.This command is a starting policy, not a universal template. A network API requires connectivity. A database may need a larger PID budget and writable storage with specific durability behavior. Security comes from explaining each grant, not maximizing the number of flags.
Loading simulation...
While the worker is running, use another host shell to resolve its PID and inspect kernel state:
Inspect runtime configuration independently:
Some fields or security attributes depend on the runtime, kernel, and enabled Linux security modules. Missing output is not proof that a protection exists or does not exist; confirm how the platform represents it.
Verification should also test behavior. Confirm that the worker can read /input, write /output and /tmp, and complete its normal job. Then confirm that writes elsewhere, unexpected network access, and privilege-gaining execution fail as intended.
After the job has exited and its evidence has been collected, remove the stopped example container:
Return to the vulnerable image processor.
If it runs as root with broad capabilities, a writable root, sensitive host mounts, unrestricted egress, and no syscall filter, arbitrary code execution gives the attacker many paths to expand control.
A layered policy changes the reachable outcomes:
Starting from an attacker who has gained code execution, each layer removes something before what remains is the application's allowed authority:
| Layer | What it reduces |
|---|---|
| Non-root and user mapping | Identity authority |
| Capability drop and no-new-privileges | Administrative authority |
| Seccomp | Kernel entry points |
| MAC and mount policy | Reachable objects and permitted mutation |
| Network policy | Reachable services |
| Cgroups | Resource abuse |
The attacker may still read data the service legitimately needs, make requests through allowed connections, and abuse application credentials. Operating-system containment cannot decide which customer record belongs to which user. Application authorization, credential design, monitoring, and incident response remain necessary.
Defense in depth does not promise that every layer stops every attack. Its purpose is to ensure that one bug or one configuration failure does not automatically grant every form of authority.
5 quizzes