// Engineering Log
Secure Development: Part 6 — Container Security
Published on 2026-09-22
// Fast route
This article belongs to the topic Security and protection.
A container isolates an application from the host, but the isolation is not absolute: all containers share one kernel. A vulnerability in the image, a process running as root, or excessive privileges can turn a bug in the application into a problem for the entire server. This section is about what to do with an already built image and how to run it. Correct Dockerfile building (cache, multi-stage builds, pinning by digest) is covered in part 5, dependency checking is in part 4.
Where risks come from
An image consists of layers: a base image with distribution packages, application dependencies, and the code itself. Vulnerabilities most often come not from your code but from the base image: a full python:3.13 or node:22 contains a compiler, package manager, shell, and hundreds of libraries that the running application doesn’t need. Each of them is a potential vulnerability and a ready-made tool for an attacker if they get inside the container.
The second group of risks is runtime parameters: process running as root, write access to any part of the filesystem, full set of kernel capabilities, lack of memory and process limits.
Minimal base image
The smaller the image, the smaller the attack surface and the fewer findings from scanners.
-slim(python:3.13-slim,node:22-slim) — Debian without build tools and documentation. A sensible default: compatibility with glibc is preserved, size is noticeably smaller than the full image.- Alpine — a very small image based on musl libc. Suitable for statically compiled programs; for Python and Node.js with native modules there can be compatibility issues and longer builds.
- Distroless from Google — the image contains only the runtime, without a shell or package manager. Current images are based on Debian 13:
gcr.io/distroless/static-debian13for static binaries (Go, Rust),gcr.io/distroless/python3-debian13,gcr.io/distroless/nodejs22-debian13and others. Most have a:nonroottag — the process starts non-root by default, and a:debugtag with a busybox shell for debugging.
Do not use unsupported versions: Python 3.9 and Node.js 18 no longer receive security fixes. For new images — Python 3.12 or 3.13, Node.js 22 or 24.
The lack of a shell in a distroless image is both protection and inconvenience: docker exec -it … sh will not work. To investigate issues use the :debug variant in a test environment or a separate debug container, rather than adding a shell to the production image.
Run non-root
By default the process in a container runs as root. If an attacker escapes the application, they will have maximal privileges inside the container, and with a vulnerability in the runtime there is a chance to get root on the host.
Create a user in the Dockerfile and switch to it:
FROM python:3.13-slim
RUN useradd --system --uid 10001 --no-create-home app
WORKDIR /app
COPY --chown=app:app . .
USER app
CMD ["python", "main.py"]For distroless images the :nonroot tag is enough. You can also set the user at runtime: docker run --user 10001:10001 …. An application without root cannot listen on ports below 1024 — use port 8080 and publish it externally as needed.
Runtime restrictions
The docker run options (in Compose — the service keys with the same names) close most typical privilege escalation paths:
docker run -d --name app \
--read-only --tmpfs /tmp \
--cap-drop=ALL \
--security-opt no-new-privileges \
--memory 512m --cpus 1 --pids-limit 200 \
--user 10001:10001 \
registry.example.ru/app@sha256:...--read-onlymounts the root filesystem read-only: an attacker won’t be able to replace application files or write tools. Directories the application actually writes to are mounted separately:--tmpfs /tmpor a data volume.--cap-drop=ALLremoves all kernel capabilities. If the application needs something — for example,NET_BIND_SERVICEfor port 80 — add exactly that with--cap-add.--security-opt no-new-privilegesprevents processes from gaining new privileges, including viasu,sudo, and setuid binaries.--memory,--cpus,--pids-limitlimit resources: a memory leak or fork bomb in one container won’t take down the whole server.
Never run production containers with --privileged and do not mount the Docker socket (/var/run/docker.sock) into them: access to the socket equals root privileges on the host.
Seccomp and rootless mode
Seccomp filters system calls. The Docker default profile works as an allowlist of calls and blocks about 44 out of more than 300 — those that are useful for kernel attacks and are rarely needed by normal applications. Docker documentation does not recommend changing it; the main rule is not to disable it with --security-opt seccomp=unconfined, which is sometimes suggested in instructions “to make it work.”
Rootless mode runs not only containers but also the Docker daemon under an ordinary user. Even if an attacker escapes a container, they gain the rights of that user, not root. Installation requires the utilities newuidmap and newgidmap (package uidmap) and at least 65,536 subordinate IDs in /etc/subuid and /etc/subgid; then run dockerd-rootless-setuptool.sh install as the user. Rootless mode reduces some capabilities (networking, privileged ports), so test it on your stack before switching.
Scanning images
A built image must be checked for known vulnerabilities before deployment and regularly after: new CVEs are found in already published packages. The same Trivy that checks dependencies (see part 4) also scans the whole image:
trivy image --severity HIGH,CRITICAL --exit-code 1 registry.example.ru/app:1.4.2With --exit-code 1 the scan will fail the pipeline on high-severity findings. Scan results for -slim or distroless images are usually an order of magnitude shorter than for full images — the cheapest way to reduce the list of vulnerabilities.
Signing and verifying images
Signing guarantees that the image deployed to the server is the one built by your CI, not swapped in the registry. The standard tool is cosign from the Sigstore project:
cosign generate-key-pair
cosign sign --key cosign.key registry.example.ru/app@sha256:...
cosign verify --key cosign.pub registry.example.ru/app@sha256:...Sign and verify by digest, not by tag: a tag can be reassigned to another image. Cosign also supports a keyless mode, with confirmation via an OIDC provider. Signature verification only makes sense if it’s enforced in deployment: the deploy script or a Kubernetes controller refuses to run an unsigned image.
Checklist
- Base image
-slimor distroless, supported runtime version, pinned by digest. USERis not root; application port above 1024.- Run with
--read-only,--cap-drop=ALL,no-new-privileges, memory/CPU/process limits. - No
--privileged, no Docker socket inside the container, and noseccomp=unconfined. - Scan the image in CI and regularly for running images.
- Sign the image in CI and verify the signature at deployment.
- Where possible — Docker rootless mode.
Common mistakes
- “A container is a sandbox.” All containers share the host kernel; isolation depends on the rights and restrictions you set.
- Default root. The most common audit finding — and the easiest to fix.
- Disabled seccomp or
--privilegedleft from debugging in production configuration. - Scanning only at build time. An image built six months ago may have accumulated dozens of new CVEs.
- Using the
latesttag in production. It’s unclear what is actually running, and you cannot verify the signature.
// Similar task
If you are dealing with something similar
This article belongs to one of the main working topics. You can keep reading on the topic, go to the homepage to understand what I do, or open the service pages directly.
Article topic
Security and protection
SSL, hardening, access control, service protection, and secure configurations.
Typical tasks behind this topic
- Set up SSL, certificates, and secure connections
- Restrict access and close unnecessary entry points
- Harden server and service configuration
// Next step
If you need help with this topic, not just another article, it is better to go straight to the service page. The homepage and topic collection stay available as secondary routes.
Open services// Reviews
Related reviews
The collaboration left an extremely positive impression, primarily because of the professionalism and the approach to resolving issues as they arose.
The experience of working together left an extremely positive impression, above all because of the professionalism and the approach to solving the issues that arose.
Jitsi Meet: a personal Zoom — setup in Docker and on a VPS
2025-11-11 · ★ 5/5
I needed to get n8n, Redis, and the database working. I had hired another contractor before and everything kept breaking. I hired Mikhail, and the next day everything was working quickly, like clockwork!
There was a task to get n8n, redis and the database working. I had previously ordered from another contractor, it kept breaking all the time. Ordered from Mikhail, the next day everything started working fast, like …
n8n installation on your VPS server. Configuration of n8n, Docker, AI, Telegram
2025-09-24 · ★ 5/5
Thank you for the fast and excellent work. Everything was done promptly and just as needed!
Thank you for the quick and good work. Everything was done promptly and as needed!
n8n installation on your VPS server. Configuration of n8n, Docker, AI, Telegram
2025-09-06 · ★ 5/5
Quick solution — I highly recommend Mikhail as a contractor! I tried to build a similar configuration myself and even followed AI advice, which ended up costing a lot of time and money (due to server downtime). So my advice: hire professionals — it's cheaper =) Thanks to Mikhail for his professionalism.
Quick fix for the problem, I recommend Mikhail as a contractor to everyone! I tried to assemble a similar configuration myself and following advice from neural networks, which resulted in a lot of wasted effort and …
n8n installation on your VPS server. Configuration of n8n, Docker, AI, Telegram.
2025-08-25 · ★ 5/5
Mikhail completed the setup of another VPS. He quickly and professionally bypassed certain hosting providers' restrictions.
Mikhail completed the setup of another VPS. Quickly, professionally bypassing certain limitations of hosting providers.
n8n installation on your VPS server. n8n, Docker, AI, Telegram setup
2025-08-12 · ★ 5/5
Great job, thank you! Mikhail is a true professional — I recommend him!
Excellent work, thank you! Mikhail is a professional in his field, I recommend him!
N8n installation on your VPS server. Setup of n8n, Docker, AI, Telegram
2025-07-03 · ★ 5/5
// Contact
Need help?
Get in touch with me and I'll help solve the problem
I reply within one business day (03:00-13:00 GMT)
Или оставьте заявку здесь:
// Related