cswork / libraryOpen practice workspace

Articles

Container Security Interview Questions: Capabilities, Seccomp, and Rootless Boundaries

Practice container security interviews with a least-privilege profile, capabilities versus seccomp, rootless daemon boundaries, and failure tests.

“We run containers as rootless, so they are isolated from the host.” That is a weak container-security interview answer. Rootless mode reduces the privilege of a runtime and its containers on the host, but a container still shares a kernel with other containers and the host. A good answer names the attacker action, finds the control that would restrict it, and states what that control cannot cover. Capabilities, seccomp, mounts, namespaces, filesystem policy, daemon access, and runtime mode each answer different questions.

This exercise assumes Linux containers under Docker or a comparable OCI-style runtime. The exact defaults vary by runtime, kernel, distribution, and orchestrator. Docker's security overview and the Linux capability manual are the starting references.

Interview prompt: harden one application without guessing

An HTTP worker accepts public requests on a high, unprivileged container port, reads a mounted configuration file, writes only to a designated temporary directory, and sends requests to one internal API. It does not need to mount filesystems, inspect host processes, manipulate network interfaces, or access host devices. A teammate proposes --privileged because one diagnostic command failed during startup; another says --cap-drop=ALL plus rootless mode makes the container safe. Design a minimal profile and a test plan. What changes if the application must bind a host low port or write to a mounted secret?

Ask for the runtime configuration and failure evidence first. Is the diagnostic command essential? Which syscall returned which errno? Which UID and GID is the process running as? Is the failing path inside the image, a read-only bind mount, or a host volume with mismatched ownership? Is the daemon rootless or merely using user-namespace remapping? A successful local run does not prove the same security context works in Kubernetes.

The answer is a justified set of restrictions that lets the worker do its job, with tests that distinguish a bug from an expected denial. Use --privileged only for a specifically justified trusted workload; Docker documents its broad device access and relaxed isolation, so it is no cure for a mysterious startup error.

First identify which boundary owns the action

Attempted actionMain control to inspectWhat that control does not settle
Perform a privileged kernel operationLinux capability set and process credentialsWhether the relevant syscall is also blocked by seccomp
Invoke a syscall denied by runtime profileSeccomp filterFile contents, object-level permission, and network destination
Read or write a mounted secretMount exposure, mode, UID/GID, MAC policySeccomp alone does not classify a secret path
Reach another serviceNetwork namespace and egress policyA process can make an allowed connect to a forbidden destination unless network policy restricts it
Control the container runtime socketSocket mount and daemon API authorizationProcess non-root inside the container does not make a writable daemon socket harmless
Escape through a kernel vulnerabilityPatch level and stronger isolation boundaryHardening layers reduce surface but do not create a separate guest kernel

This table is the first topic-specific interview asset: it prevents the common category error of saying “seccomp blocks writes to secret files” or “rootless means no network egress.” Filesystem and network restrictions must be designed separately. A kernel may apply multiple checks to one action. A failure can be caused by missing capability, seccomp, Linux discretionary access control, AppArmor/SELinux, mount flags, or a user-namespace mapping; inspect evidence rather than identifying the cause from an error string alone.

Capabilities: reduce privilege, not every possible operation

Linux capabilities split traditionally root-associated powers into separately controlled units. The manual page lists them, including CAP_NET_ADMIN for network administration and the broadly overloaded CAP_SYS_ADMIN for many system administration tasks. The exact privilege check depends on the operation and namespace. Dropping a capability can prevent a process from passing a particular kernel permission check; it does not stop ordinary allowed syscalls or make the file tree invisible.

For the HTTP worker, begin by dropping all capabilities in a controlled test, then add back only a documented capability that the required workload proves it needs. In Docker, --cap-drop=ALL and a narrow --cap-add=... are supported flags. Do not add SYS_ADMIN to make a mount failure disappear without asking why the app is mounting anything. Do not assume “non-root user” and “no capabilities” are synonyms: file capabilities, runtime configuration, and namespace-scoped privilege can still affect a process. Inspect the effective/bounding sets in the running process when debugging.

Binding a low port is a good interview trap. CAP_NET_BIND_SERVICE is the traditional capability for privileged ports below 1024, but some runtimes and network namespaces configure the unprivileged-port threshold differently. The worker can often listen on a high container port while an ingress or host mapping exposes port 80 externally, avoiding a capability in the container. If direct low-port binding is a real requirement, inspect the actual runtime threshold and only then decide whether a narrow capability is needed. A host port mapping is not the same as the process calling bind(2) on port 80 inside the container.

Seccomp: filter syscalls, not business objects

The Linux kernel's seccomp filter documentation describes a filter over syscall number and arguments. Docker's default seccomp profile is a compatibility-minded allowlist/deny-by-default design that blocks a subset of risky syscalls while permitting common application behavior. “Default” is not synonymous with a perfect sandbox; it is a useful baseline whose actual content and runtime support must be checked for the version in use.

Suppose the diagnostic invokes mount(2) and fails. The default seccomp profile may block that syscall, and the process may also lack CAP_SYS_ADMIN. Removing seccomp or adding the capability would widen the attack surface for a diagnostic that the application does not need. The first fix is to remove the unnecessary operation or run diagnostics in a separate, explicitly authorized environment. If a production workload genuinely requires a specific syscall, test a narrowly customized profile against its normal and failure paths; do not replace the default with unconfined as a routine answer.

Seccomp cannot generally express “permit openat on this safe path but deny openat on /run/secrets” by treating a pointer argument as a trusted pathname. That belongs to mounts, file permissions, and potentially an LSM policy. Likewise, allowing connect does not define permitted remote services; an egress rule or proxy does. A robust answer treats seccomp as reducing the syscall surface of a process, not as an application authorization policy. If a syscall is blocked, record the exact syscall, architecture, profile version, and process context before changing the policy.

Rootless mode: whose root is removed?

Docker rootless mode runs the Docker daemon and containers as a non-root user in a user namespace. That is different from userns-remap, where containers use remapped UIDs but the daemon still runs with root privileges. It is also different from setting USER 10001 in an image while a rootful daemon remains in charge. All three can be useful, but they address distinct privilege boundaries.

Rootless mode reduces the consequences of a compromised daemon or runtime because those components do not start with host root privilege. It does not make every file inaccessible, deny every syscall, or provide a separate kernel. The non-root host user still has access to whatever its account and configured mounts permit. A writable host path, a mounted credential, a permissive network, or an exploitable shared-kernel interface remains relevant. Docker documents prerequisites and limitations, so check storage, networking, UID/GID mapping, and operational tooling before making it a production default.

The application process identity deserves separate attention. A container may display UID 0 inside its user namespace while mapping to an unprivileged host UID. That is not equivalent to unrestricted host root, but it does not justify ignoring in-container permissions or writable mounts. Set the image/runtime process user deliberately when possible, and inspect the host mapping. A Kubernetes security context such as runAsNonRoot controls the process identity at a different layer than whether the underlying node's daemon is rootless.

Turn the profile into a testable deployment

Start with a non-root application UID, minimal filesystem access, a read-only root filesystem if the app supports it, an explicit writable temporary mount, only required read-only configuration mounts, no host device or Docker socket mounts, dropped unnecessary capabilities, and the runtime's default seccomp profile. Restrict egress to the needed internal API and keep resource limits and logging in the deployment plan. These are design choices to verify against the application, not flags to copy blindly into every service.

Test the expected path and expected denials. The worker must accept a request, read its configuration, write only to its scratch directory, and reach the one allowed API. A test should fail when it tries to modify a read-only config, access a host device, create a mount, or reach a forbidden egress destination. Record the observed enforcement layer for each denial, because an accidental ENOENT is not proof that the security control worked. Run the same tests in CI and on a staging node with the production runtime configuration and kernel profile; note if a library upgrade adds a syscall the profile does not allow.

If the deployment uses Kubernetes, compare the proposed Pod security context with its Restricted Pod Security Standard: among other Linux-specific rules, Restricted constrains privilege escalation, capabilities, and seccomp configuration. Pod Security Admission can enforce a namespace policy, but admission checks do not replace runtime monitoring or network policy. The profile must also cover init and ephemeral containers where they are permitted. A privileged sidecar or hostPath mount can undo the isolation story of a hardened main container.

When an application fails under the hardened profile, make one change at a time. Capture the failing syscall or permission check, prove the operation is required, grant the narrowest viable permission, and rerun both positive and negative tests. Log runtime version and the final diff in security context. A rollback may restore availability temporarily, but it should be explicit, time-bounded, and observed; silently switching to privileged or seccomp=unconfined destroys the evidence that the profile still protects the workload.

Where the shared-kernel boundary ends

Containers use the host kernel. Namespaces change what processes see; cgroups limit resource consumption; capabilities and seccomp reduce privileged operations and syscall surface; MAC policies and mounts constrain access. These layers complement one another but are not equivalent to a VM with a distinct guest kernel. For untrusted arbitrary code or mutually distrustful tenants, discuss stronger isolation such as a microVM or VM, alongside cost, startup time, device/GPU support, and operational complexity. Do not claim a microVM is automatically safe either; it changes the boundary and still needs patching and policy.

A trusted internal worker may fit ordinary container controls. A platform accepting adversarial code may need a stronger substrate, strict egress, short-lived credentials, and host placement controls. Name the attacker and asset before choosing an isolation level; “we use Docker” is not a complete security argument.

A concise answer to give aloud

“I would first list the worker's required operations and mounts. I would run it as a deliberately unprivileged process, drop capabilities by default, keep the runtime's seccomp profile, avoid host socket/devices, make the root filesystem read-only where feasible, and allow only specific writable paths and egress. Capabilities govern privileged operations; seccomp filters syscalls; mounts and network policy control exposed files and destinations. Rootless mode changes the host privilege of the daemon and containers, but does not give a separate kernel or erase dangerous mounts. I would test required behavior and expected denials on the production runtime, inspect which layer caused each failure, and grant back only what the workload proves it needs. For adversarial code, I would evaluate a stronger isolation boundary.”

Every proposed control has a job and an observable test. No flag is credited with properties it lacks. This profile is editorial reasoning for a hypothetical worker; use current runtime documentation and a real workload trace before production.

Sources and Further Reading