Security
Running as non-root, read-only filesystems, secrets management, and image scanning. You'll hit this when a security audit flags your containers for running as root with write access everywhere.
FROM node:20-alpine
WORKDIR /app
COPY . .
RUN npm ci
# Runs as root by default
CMD ["node", "server.js"]FROM node:20-alpine
WORKDIR /app
COPY . .
RUN npm ci
# Runs as root by default
CMD ["node", "server.js"]FROM node:20-alpine
WORKDIR /app
COPY . .
RUN npm ci
# Create and switch to non-root user
RUN addgroup -S appgroup && \
adduser -S appuser -G appgroup
USER appuser
CMD ["node", "server.js"]FROM node:20-alpine
WORKDIR /app
COPY . .
RUN npm ci
# Create and switch to non-root user
RUN addgroup -S appgroup && \
adduser -S appuser -G appgroup
USER appuser
CMD ["node", "server.js"]Containers run as root by default. If an attacker gains code execution, they have root access inside the container. Combined with misconfigurations (privileged mode, host mounts), this can lead to container escape and host compromise.
Running as a non-root user limits what an attacker can do if they exploit a vulnerability. The appuser cannot install packages, modify system files, or access other users' data. This is a fundamental container security practice.
FROM node:20-alpine
WORKDIR /app
COPY . .
# Secret baked into image layer
ENV DATABASE_URL="postgres://admin:s3cret@db:5432/app"
ENV API_KEY="sk-live-abc123def456"
RUN npm ci
CMD ["node", "server.js"]FROM node:20-alpine
WORKDIR /app
COPY . .
# Secret baked into image layer
ENV DATABASE_URL="postgres://admin:s3cret@db:5432/app"
ENV API_KEY="sk-live-abc123def456"
RUN npm ci
CMD ["node", "server.js"]FROM node:20-alpine
WORKDIR /app
COPY . .
RUN npm ci
# Secrets injected at runtime
# via docker run --env-file
# or Kubernetes secrets
CMD ["node", "server.js"]FROM node:20-alpine
WORKDIR /app
COPY . .
RUN npm ci
# Secrets injected at runtime
# via docker run --env-file
# or Kubernetes secrets
CMD ["node", "server.js"]ENV instructions are stored in the image layer history. Running docker history or docker inspect reveals every ENV value. Pushing this image to a registry exposes your credentials to anyone with pull access.
Secrets should be injected at runtime via environment variables, Docker secrets, or Kubernetes Secrets. This keeps credentials out of image layers, version control, and container registries. Anyone with access to the image can extract baked-in secrets.
services:
app:
image: myapp:1.0
# Default: writable filesystem
# Attacker can write malwareservices:
app:
image: myapp:1.0
# Default: writable filesystem
# Attacker can write malwareservices:
app:
image: myapp:1.0
read_only: true
tmpfs:
- /tmp
- /app/cacheservices:
app:
image: myapp:1.0
read_only: true
tmpfs:
- /tmp
- /app/cacheA writable filesystem lets an attacker write and execute malicious binaries, modify application code, or install backdoors. Even without root access, writing to /tmp or the app directory can enable further exploitation.
A read-only root filesystem prevents attackers from writing scripts, downloading tools, or modifying application code inside the container. tmpfs mounts provide writable directories for legitimate temporary files without persisting anything to disk.
apiVersion: v1
kind: Pod
metadata:
name: app
spec:
containers:
- name: app
image: myapp:1.0
env:
# Secret visible in pod spec,
# logs, and process listing
- name: DB_PASSWORD
value: "s3cret-passw0rd"apiVersion: v1
kind: Pod
metadata:
name: app
spec:
containers:
- name: app
image: myapp:1.0
env:
# Secret visible in pod spec,
# logs, and process listing
- name: DB_PASSWORD
value: "s3cret-passw0rd"apiVersion: v1
kind: Pod
metadata:
name: app
spec:
containers:
- name: app
image: myapp:1.0
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: passwordapiVersion: v1
kind: Pod
metadata:
name: app
spec:
containers:
- name: app
image: myapp:1.0
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: passwordHardcoding secrets in pod specs means they appear in plain text in kubectl get pod -o yaml, etcd backups, audit logs, and version control. Anyone with read access to the namespace can see the credentials.
Using secretKeyRef reads the value from a Kubernetes Secret object. The secret is stored encrypted (at rest), access is controlled by RBAC, and the plain text value doesn't appear in the pod spec or kubectl describe output.
apiVersion: v1
kind: Pod
metadata:
name: app
spec:
containers:
- name: app
image: myapp:1.0
# Default capabilities include
# NET_RAW, SYS_CHROOT, MKNOD...apiVersion: v1
kind: Pod
metadata:
name: app
spec:
containers:
- name: app
image: myapp:1.0
# Default capabilities include
# NET_RAW, SYS_CHROOT, MKNOD...apiVersion: v1
kind: Pod
metadata:
name: app
spec:
containers:
- name: app
image: myapp:1.0
securityContext:
runAsNonRoot: true
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop:
- ALLapiVersion: v1
kind: Pod
metadata:
name: app
spec:
containers:
- name: app
image: myapp:1.0
securityContext:
runAsNonRoot: true
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop:
- ALLDefault container capabilities include NET_RAW (ARP spoofing, network sniffing), MKNOD (device file creation), and others. An attacker who compromises the container can leverage these capabilities for lateral movement and privilege escalation.
Dropping all Linux capabilities removes permissions the container doesn't need, like raw network access (NET_RAW), filesystem mounting (SYS_ADMIN), and process tracing (SYS_PTRACE). Combined with runAsNonRoot and read-only filesystem, this creates a hardened container.