Learn

/

Security

Security

5 patterns

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.

Avoid
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"]

Prefer
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"]
Why avoid

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.

Why prefer

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.

Docker docs: USER
Avoid
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"]

Prefer
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"]
Why avoid

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.

Why prefer

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.

Docker docs: Build secrets
Avoid
services:
  app:
    image: myapp:1.0
    # Default: writable filesystem
    # Attacker can write malware
services:
  app:
    image: myapp:1.0
    # Default: writable filesystem
    # Attacker can write malware

Prefer
services:
  app:
    image: myapp:1.0
    read_only: true
    tmpfs:
      - /tmp
      - /app/cache
services:
  app:
    image: myapp:1.0
    read_only: true
    tmpfs:
      - /tmp
      - /app/cache
Why avoid

A 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.

Why prefer

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.

Docker docs: read_only
Avoid
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"

Prefer
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: password
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: password
Why avoid

Hardcoding 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.

Why prefer

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.

Kubernetes docs: Secrets
Avoid
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...

Prefer
apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  containers:
    - name: app
      image: myapp:1.0
      securityContext:
        runAsNonRoot: true
        readOnlyRootFilesystem: true
        allowPrivilegeEscalation: false
        capabilities:
          drop:
            - ALL
apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  containers:
    - name: app
      image: myapp:1.0
      securityContext:
        runAsNonRoot: true
        readOnlyRootFilesystem: true
        allowPrivilegeEscalation: false
        capabilities:
          drop:
            - ALL
Why avoid

Default 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.

Why prefer

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.

Kubernetes docs: Security context