KubeKanvas Logo
  • Features
  • Pricing
  • Templates
    • How KubeKanvas works
    • Blog
  • FAQs
  • Contact
  • Features
  • Pricing
  • Templates
    • How KubeKanvas works
    • Blog
  • FAQs
  • Contact

Securing Kubernetes Pods: A Complete Guide to Pod-Level Security Configuration

Complete guide to securing Kubernetes pods: security contexts, secrets management, image security, resource limits, and Linux security modules.
Shamaila Mahmood
Shamaila Mahmood
October 29, 2025
Kubernetes
Securing Kubernetes Pods: A Complete Guide to Pod-Level Security Configuration

Securing Kubernetes Pods: A Complete Guide to Pod-Level Security Configuration

When deploying applications to Kubernetes, security should be your top priority, not an afterthought. While Kubernetes provides numerous security features at the cluster and network level, many critical security configurations happen right at the pod level—in your YAML manifests. A single misconfigured pod can become the entry point for attackers or cause significant security vulnerabilities.

The Tesla Cryptojacking Attack

In 2018, Tesla discovered that attackers had compromised their Kubernetes infrastructure and were using it to mine cryptocurrency. The breach happened because their Kubernetes console was exposed to the internet without password protection. Once inside, the attackers deployed cryptomining containers that silently consumed Tesla's cloud resources.

What makes this particularly relevant to pod security? The attackers specifically:

  • Deployed pods without resource limits, allowing them to consume as much CPU as possible
  • Configured the mining software to run at low utilization to avoid detection
  • Used the compromised environment to access AWS credentials stored insecurely

This attack could have been prevented or detected early with proper pod-level security configurations: resource limits would have constrained the mining pods, proper secret management would have protected the AWS credentials, and security contexts would have limited what the containers could access.

The lesson is clear: Pod security isn't just about best practices—it's about preventing real-world attacks that can cost organizations millions in cloud bills and reputational damage.


This comprehensive guide covers every security configuration you should implement in your pod specifications to ensure your workloads run securely by default.

Security Context: The Foundation of Pod Security

The securityContext is your primary tool for implementing security controls at the pod and container level. It allows you to configure various security-related settings that govern how your containers run.

Running as Non-Root User

Never run containers as root unless absolutely necessary. Most applications don't need root privileges and running as root violates the principle of least privilege.

apiVersion: v1
kind: Pod
metadata:
  name: secure-app
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    runAsGroup: 1000
    fsGroup: 1000
  containers:
  - name: app
    image: myapp:v1.2.3
    securityContext:
      runAsNonRoot: true
      runAsUser: 1000
      allowPrivilegeEscalation: false
      readOnlyRootFilesystem: true

Key configurations:

  • runAsNonRoot: true - Ensures the container cannot run as root
  • runAsUser: 1000 - Specifies the user ID to run the container
  • runAsGroup: 1000 - Specifies the primary group ID
  • fsGroup: 1000 - Sets the group for volume ownership

Preventing Privilege Escalation

Privilege escalation occurs when a process gains higher privileges than it was originally granted, often exploiting vulnerabilities to gain root or administrative access. In containers, this can happen when a process uses setuid binaries, capabilities, or other mechanisms to gain elevated permissions. Preventing privilege escalation is crucial because it stops attackers from gaining root access even if they compromise your application.

securityContext:
  allowPrivilegeEscalation: false
  capabilities:
    drop:
      - ALL
    add:
      - NET_BIND_SERVICE  # Only if you need to bind to ports < 1024
  • allowPrivilegeEscalation: false - Prevents processes from gaining more privileges than their parent
  • capabilities.drop: [ALL] - Removes all Linux capabilities
  • Only add specific capabilities your application needs

Read-Only Root Filesystem

Making the root filesystem read-only prevents malicious code from modifying system files:

securityContext:
  readOnlyRootFilesystem: true

When using read-only filesystems, mount writable volumes for directories that need write access:

volumeMounts:
- name: tmp-volume
  mountPath: /tmp
- name: var-log
  mountPath: /var/log
volumes:
- name: tmp-volume
  emptyDir: {}
- name: var-log
  emptyDir: {}

Linux Capabilities

Linux capabilities provide fine-grained privilege control. Always follow the principle of least privilege:

securityContext:
  capabilities:
    drop:
      - ALL
    add:
      - NET_BIND_SERVICE    # Bind to ports < 1024
      - CHOWN              # Change file ownership (if needed)
      # Only add capabilities your application actually needs

Common capabilities and their use cases:

  • NET_BIND_SERVICE - Bind to privileged ports (< 1024)
  • NET_ADMIN - Network administration (usually not needed)
  • SYS_TIME - Set system time (usually not needed)
  • CHOWN - Change file ownership
  • DAC_OVERRIDE - Bypass file permission checks (dangerous)

Using Secrets Instead of Plain Text

Never store sensitive information in plain text in your pod specifications. Always use Kubernetes Secrets for passwords, API keys, certificates, and other sensitive data.

Bad Practice (DON'T DO THIS):

# ❌ NEVER DO THIS
containers:
- name: app
  image: myapp:v1.2.3
  env:
  - name: DATABASE_PASSWORD
    value: "my-super-secret-password"
  - name: API_KEY
    value: "sk-1234567890abcdef"

Better Practice:

First, create a Secret:

apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
type: Opaque
data:
  database-password: bXktc3VwZXItc2VjcmV0LXBhc3N3b3Jk  # base64 encoded
  api-key: c2stMTIzNDU2Nzg5MGFiY2RlZg==  # base64 encoded

Then reference it in your pod:

apiVersion: v1
kind: Pod
metadata:
  name: secure-app
spec:
  containers:
  - name: app
    image: myapp:v1.2.3
    env:
    - name: DATABASE_PASSWORD
      valueFrom:
        secretKeyRef:
          name: app-secrets
          key: database-password
    - name: API_KEY
      valueFrom:
        secretKeyRef:
          name: app-secrets
          key: api-key

Using Secrets as Volumes

For configuration files or certificates, mount secrets as volumes:

containers:
- name: app
  image: myapp:v1.2.3
  volumeMounts:
  - name: app-secrets
    mountPath: /etc/secrets
    readOnly: true
volumes:
- name: app-secrets
  secret:
    secretName: app-secrets
    defaultMode: 0400  # Read-only for owner only

Important: Kubernetes Secrets Are Not Really "Secret"

A critical caveat: While Kubernetes Secrets are better than plain text environment variables, they're not truly secure by default. Here's why:

  • Base64 is encoding, not encryption - Secrets are only base64 encoded, which anyone can easily decode
  • Stored in etcd - By default, secrets are stored unencrypted in etcd (though encryption at rest can be enabled)
  • Accessible via API - Anyone with API access can retrieve secrets
  • Visible in pod specs - Secrets mounted as environment variables can be seen by anyone who can describe the pod

What we've shown here is simply a better alternative to plain text - it prevents secrets from being accidentally committed to Git or visible in logs, but it's not a complete security solution.

For production environments, you need proper secret management solutions like:

  • HashiCorp Vault - Centralized secret management with encryption and access control
  • External Secrets Operator - Sync secrets from external systems into Kubernetes
  • Cloud provider secret managers - AWS Secrets Manager, Azure Key Vault, GCP Secret Manager
  • Sealed Secrets - Encrypt secrets so they can be safely stored in Git

Since secret management is a comprehensive topic that deserves its own deep dive, we'll cover enterprise-grade secret management strategies, encryption at rest, secret rotation, and integration with external secret stores in a dedicated article. For now, just remember: Kubernetes Secrets are better than plain text, but they're not the final answer for production security.


Container Image Security

Your container images are a critical attack surface. Implement these practices to ensure image security.

Never Use Latest Tags

Always pin specific image versions instead of using latest tags:

# ❌ DON'T DO THIS
containers:
- name: app
  image: myapp:latest

# ✅ DO THIS
containers:
- name: app
  image: myapp:v1.2.3
  # Or use digest for immutability
  # image: myapp@sha256:abc123...

Why avoid latest?

  • Unpredictable deployments
  • Security vulnerabilities in newer versions
  • Difficult to reproduce issues
  • No rollback strategy

Use Minimal Base Images

Choose secure, minimal base images:

# ✅ Good choices
FROM gcr.io/distroless/java17-debian11:latest
FROM alpine:3.18
FROM scratch  # For static binaries

# ❌ Avoid if possible
FROM ubuntu:latest
FROM centos:latest

Image Pull Policies

Control when images are pulled:

containers:
- name: app
  image: myapp:v1.2.3
  imagePullPolicy: Always  # Always pull (for security updates)
  # or
  imagePullPolicy: IfNotPresent  # Only pull if not present locally

Using Private Registries

For sensitive applications, use private registries:

apiVersion: v1
kind: Pod
metadata:
  name: secure-app
spec:
  imagePullSecrets:
  - name: private-registry-secret
  containers:
  - name: app
    image: private-registry.company.com/myapp:v1.2.3

Create the image pull secret:

kubectl create secret docker-registry private-registry-secret \
  --docker-server=private-registry.company.com \
  --docker-username=username \
  --docker-password=password \
  --docker-email=email@company.com

Resource Limits and Security

Resource limits are a security feature that prevents resource exhaustion attacks and ensures fair resource sharing.

CPU and Memory Limits

Always set resource requests and limits:

containers:
- name: app
  image: myapp:v1.2.3
  resources:
    requests:
      memory: "256Mi"
      cpu: "250m"
    limits:
      memory: "512Mi"
      cpu: "500m"

Ephemeral Storage Limits

Limit temporary storage to prevent disk space attacks:

resources:
  requests:
    ephemeral-storage: "1Gi"
  limits:
    ephemeral-storage: "2Gi"

Why Resource Limits Matter for Security

  1. Prevent DoS attacks: Malicious code can't consume all resources
  2. Limit blast radius: Compromised containers can't affect other workloads
  3. Predictable behavior: Applications behave consistently across environments
  4. Cost control: Prevent unexpected resource usage costs

Linux Security Modules Integration

Kubernetes integrates with Linux security modules like SELinux, AppArmor, and seccomp to provide additional security layers.

SELinux Integration

What is SELinux? Security-Enhanced Linux (SELinux) is a Linux kernel security module that provides mandatory access control (MAC). Unlike traditional discretionary access control (DAC) where users control access to their own files, SELinux enforces system-wide security policies that even root users must follow. Think of it as an additional layer of protection where every process and file has a security label, and the kernel enforces rules about what can access what.

Why use SELinux with Kubernetes? Even if a container is compromised, SELinux restricts what the attacker can do by enforcing strict policies about file access, network operations, and inter-process communication. It's like having a security guard that checks permissions even after someone has the keys.

apiVersion: v1
kind: Pod
metadata:
  name: selinux-secured-app
spec:
  securityContext:
    seLinuxOptions:
      level: "s0:c123,c456"    # Multi-Level Security (MLS) level
      role: "object_r"          # SELinux role
      type: "container_t"       # SELinux type (most important)
      user: "system_u"          # SELinux user
  containers:
  - name: app
    image: myapp:v1.2.3

Understanding SELinux Options:

  • type (most important): Defines what the process can access. container_t is the default type for containers, which has restricted access to system resources. This is the primary security boundary.

  • level: Used for Multi-Level Security (MLS) to implement sensitivity levels (like "top secret" vs "public"). The format is sensitivity:category. For example, s0:c123,c456 means sensitivity level 0 with categories 123 and 456. Processes can only access resources at their level or below.

  • role: Defines what types a user can transition to. object_r is the standard role for files and objects. Roles are less commonly customized in containers.

  • user: SELinux user identity (not the same as Linux user). system_u is the system user for system processes.

Practical Example:

# A pod that can only access specific resources
securityContext:
  seLinuxOptions:
    type: "container_t"        # Standard container type
    level: "s0:c100"          # Can only access resources at s0:c100

Note: SELinux must be enabled on your nodes for these settings to take effect. Many Kubernetes distributions (like OpenShift) enable SELinux by default, while others (like many managed Kubernetes services) may not. You can check if SELinux is enabled on your nodes with:

kubectl debug node/your-node -it --image=busybox -- chroot /host sestatus

If SELinux is not enabled on your nodes, these options will be ignored (they won't cause errors, but they won't provide protection either).

AppArmor Profiles

What is AppArmor? AppArmor (Application Armor) is a Linux security module that restricts programs' capabilities using per-program profiles. Similar to SELinux, it provides mandatory access control but with a simpler, path-based approach. AppArmor profiles define what files a program can access, which capabilities it can use, and what network access it has, creating an additional security layer that contains damage if a container is compromised.

apiVersion: v1
kind: Pod
metadata:
  name: apparmor-secured-app
spec:
  securityContext:
    appArmorProfile:
      type: RuntimeDefault
      # or use a custom profile:
      # type: Localhost
      # localhostProfile: custom-profile-name
  containers:
  - name: app
    image: myapp:v1.2.3

AppArmor profile types:

  • RuntimeDefault - Use the container runtime's default AppArmor profile
  • Localhost - Use a custom profile loaded on the node
  • Unconfined - No AppArmor restrictions (not recommended)

Seccomp Profiles

What is Seccomp? Secure Computing Mode (seccomp) is a Linux kernel feature that restricts which system calls (syscalls) a process can make. Since most applications only use a small subset of available system calls, seccomp acts as a filter that blocks dangerous or unnecessary syscalls that attackers might exploit. By limiting the attack surface at the kernel level, seccomp prevents compromised containers from performing low-level operations like creating new processes, accessing raw sockets, or manipulating kernel modules—common techniques used in container breakout attacks.

apiVersion: v1
kind: Pod
metadata:
  name: seccomp-secured-app
spec:
  securityContext:
    seccompProfile:
      type: RuntimeDefault
      # or use a custom profile:
      # type: Localhost
      # localhostProfile: profiles/default.json
  containers:
  - name: app
    image: myapp:v1.2.3

Seccomp profile types:

  • RuntimeDefault - Use the container runtime's default profile
  • Unconfined - No seccomp restrictions (not recommended)
  • Localhost - Use a custom profile from the node

Custom Seccomp Profile Example

{
  "defaultAction": "SCMP_ACT_ERRNO",
  "architectures": ["SCMP_ARCH_X86_64"],
  "syscalls": [
    {
      "names": ["read", "write", "open", "close", "mmap"],
      "action": "SCMP_ACT_ALLOW"
    }
  ]
}

Service Account and RBAC Integration

Service accounts provide identity for pods within the Kubernetes cluster, allowing them to authenticate with the API server and access cluster resources. Every pod runs with a service account (using the "default" account if none is specified), which comes with associated permissions. Properly configuring service accounts and their Role-Based Access Control (RBAC) permissions is critical—overly permissive service accounts are a common attack vector, as compromised pods can use them to escalate privileges and access resources they shouldn't.

Use Dedicated Service Accounts

Don't use the default service account for applications:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: myapp-service-account
  namespace: default
automountServiceAccountToken: false  # Disable if not needed

---
apiVersion: v1
kind: Pod
metadata:
  name: secure-app
spec:
  serviceAccountName: myapp-service-account
  automountServiceAccountToken: false  # Disable API access if not needed

Disable Service Account Token Mounting

By default, Kubernetes automatically mounts a service account token into every pod, giving it credentials to communicate with the Kubernetes API server. If your application doesn't need to interact with the Kubernetes API (which is the case for most applications), this token becomes an unnecessary security risk—if an attacker compromises your container, they can use this token to query the API, potentially discovering sensitive information about your cluster or escalating their access. Disabling token mounting removes this attack vector entirely.

spec:
  automountServiceAccountToken: false

RBAC for Service Accounts

Even with a dedicated service account, you need to control what permissions it has through Role-Based Access Control (RBAC). Following the principle of least privilege, grant only the minimum permissions your application needs to function. For example, if your app only reads configuration data, grant it read-only access to ConfigMaps, not full cluster access. This ensures that even if your pod is compromised, attackers can't use the service account to access or modify other cluster resources.

Create minimal RBAC permissions:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: myapp-role
rules:
- apiGroups: [""]
  resources: ["configmaps"]
  verbs: ["get", "list"]

---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: myapp-rolebinding
subjects:
- kind: ServiceAccount
  name: myapp-service-account
roleRef:
  kind: Role
  name: myapp-role
  apiGroup: rbac.authorization.k8s.io

Network Security at Pod Level

While comprehensive network security involves NetworkPolicies (covered in a future article), there are pod-level configurations that enhance network security.

DNS Policy Configuration

Control DNS resolution behavior:

spec:
  dnsPolicy: ClusterFirst
  # or for more control:
  dnsPolicy: None
  dnsConfig:
    nameservers:
    - 8.8.8.8
    searches:
    - my.domain.com
    options:
    - name: ndots
      value: "2"

Host Network and Ports

Sharing the host's network namespace (hostNetwork: true) gives your container direct access to the node's network interfaces, which significantly increases security risks. When enabled, a compromised container can sniff network traffic, bind to any port on the host (including privileged ports), and potentially access services that should be isolated. Similarly, hostPID and hostIPC expose the host's process and inter-process communication namespaces, allowing containers to see and potentially manipulate processes running on the node itself. Unless you have a specific need (like network monitoring tools), always keep these disabled.

# ❌ DON'T DO THIS unless absolutely necessary
spec:
  hostNetwork: true
  hostPID: true
  hostIPC: true

# ✅ DO THIS (default behavior)
spec:
  hostNetwork: false  # default

Complete Secure Pod Example

Here's a comprehensive example that implements all the security best practices:

apiVersion: v1
kind: Pod
metadata:
  name: ultra-secure-app
  annotations:
    container.apparmor.security.beta.kubernetes.io/app: "runtime/default"
spec:
  serviceAccountName: myapp-service-account
  automountServiceAccountToken: false
  
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    runAsGroup: 1000
    fsGroup: 1000
    appArmorProfile:
        type: RuntimeDefault
    seccompProfile:
      type: RuntimeDefault
    seLinuxOptions:
      level: "s0:c123,c456"
  
  imagePullSecrets:
  - name: private-registry-secret
  
  containers:
  - name: app
    image: private-registry.company.com/myapp:v1.2.3
    imagePullPolicy: Always

    securityContext:
      runAsNonRoot: true
      runAsUser: 1000
      allowPrivilegeEscalation: false
      readOnlyRootFilesystem: true
      capabilities:
        drop:
        - ALL
        add:
        - NET_BIND_SERVICE
    env:
    - name: DATABASE_PASSWORD
      valueFrom:
        secretKeyRef:
          name: app-secrets
          key: database-password
    - name: API_KEY
      valueFrom:
        secretKeyRef:
          name: app-secrets
          key: api-key
    envFrom:
    - configMapRef:
        name: app-config
    resources:
      requests:
        memory: "256Mi"
        cpu: "250m"
        ephemeral-storage: "1Gi"
      limits:
        memory: "512Mi"
        cpu: "500m"
        ephemeral-storage: "2Gi"
    livenessProbe:
      httpGet:
        path: /health
        port: 8080
      initialDelaySeconds: 30
      periodSeconds: 10
    
    readinessProbe:
      httpGet:
        path: /ready
        port: 8080
      initialDelaySeconds: 5
      periodSeconds: 5
    volumeMounts:
    - name: tmp-volume
      mountPath: /tmp
    - name: app-secrets
      mountPath: /etc/secrets
      readOnly: true
    ports:
    - containerPort: 8080
      protocol: TCP
  volumes:
  - name: tmp-volume
    emptyDir: {}
  - name: app-secrets
    secret:
      secretName: app-secrets
      defaultMode: 0400

  restartPolicy: Always
  dnsPolicy: ClusterFirst

---
# Supporting resources

apiVersion: v1
kind: ServiceAccount
metadata:
  name: myapp-service-account
automountServiceAccountToken: false

---
apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
type: Opaque
data:
  database-password: bXktc3VwZXItc2VjcmV0LXBhc3N3b3Jk
  api-key: c2stMTIzNDU2Nzg5MGFiY2RlZg==

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  LOG_LEVEL: "info"
  ENVIRONMENT: "production"
  MAX_CONNECTIONS: "100"

---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: myapp-role
rules:
- apiGroups: [""]
  resources: ["configmaps"]
  verbs: ["get", "list"]

---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: myapp-rolebinding
subjects:
- kind: ServiceAccount
  name: myapp-service-account
roleRef:
  kind: Role
  name: myapp-role
  apiGroup: rbac.authorization.k8s.io

Beyond Pod Configuration

While this article focuses on pod-level security configurations, comprehensive Kubernetes security requires additional layers of defense. Important security topics that work alongside pod configuration include Network Policies for controlling traffic between pods, Pod Security Admission for enforcing cluster-wide security standards, Admission Controllers for validating pod specifications, RBAC and Authentication for access management, and Runtime Security tools like Falco for detecting anomalous behavior. We'll cover each of these critical security components in detail in upcoming articles to provide you with a complete Kubernetes security strategy.


Conclusion

Securing Kubernetes pods requires a layered approach that starts with your YAML manifests. By implementing the security practices outlined in this guide—from security contexts and secrets management to image security and resource limits—you create a strong foundation for your application security.

Remember that security is not a one-time configuration but an ongoing process. Regularly update your security practices, scan for vulnerabilities, monitor for threats, and stay informed about new security features and best practices in the Kubernetes ecosystem.

The security configurations you implement at the pod level are just the beginning. In future articles, we'll explore NetworkPolicies, Pod Security Admission, cluster hardening, and operational security practices that complete your Kubernetes security strategy.

Start with these pod-level security practices today, and you'll be well on your way to running secure, production-ready Kubernetes workloads.

Get Complete Template

Use the template to copy the manifest with all security related properties configured.

Related Articles

How to Manage Events with a Dynamic Informer in Kubernetes
How to Manage Events with a Dynamic Informer in Kubernetes
Kubernetes operators and controllers rely heavily on informers to watch and react to changes in the ...
Rafay Siddiquie
October 29, 2025
Kubernetes
Why You Should Avoid Using `latest` Tags in Kubernetes Deployments — Always Pin Your Images
Why You Should Avoid Using `latest` Tags in Kubernetes Deployments — Always Pin Your Images
Using :latest in Kubernetes might feel convenient, but it breaks reproducibility, makes rollbacks ri...
Shamaila Mahmood
Shamaila Mahmood
October 29, 2025
Kubernetes
Why I Prefer Custom Helm Charts Over Vendor-Supplied Ones
Why I Prefer Custom Helm Charts Over Vendor-Supplied Ones
Vendor Helm charts offer quick setups, but at the cost of complexity, security, and long-term mainta...
Shamaila Mahmood
Shamaila Mahmood
October 29, 2025
Kubernetes
What Is Kubernetes and Why Is It Dominating the Cloud?
What Is Kubernetes and Why Is It Dominating the Cloud?
Discover what Kubernetes is, how it evolved, and why it's become the de facto orchestration platform...
Khurram Mahmood
Khurram Mahmood
October 29, 2025
Kubernetes
KubeKanvas Logo
Visual Kubernetes cluster design tool that helps you create, manage, and deploy your applications with ease.

Product

  • Features
  • Pricing
  • Templates

Resources

  • Blog
  • Tutorials

Company

  • About Us
  • Contact
  • Terms of Service
  • Privacy Policy
  • Impressum
XGitHubLinkedIn
© 2025 KubeKanvas. All rights reserved.