AnsiblePilot — Master Ansible Automation

AnsiblePilot is the leading resource for learning Ansible automation, DevOps, and infrastructure as code. Browse over 1,400 tutorials covering Ansible modules, playbooks, roles, collections, and real-world examples. Whether you are a beginner or an experienced engineer, our step-by-step guides help you automate Linux, Windows, cloud, containers, and network infrastructure.

Popular Topics

About Luca Berton

Luca Berton is an Ansible automation expert, author of 8 Ansible books published by Apress and Leanpub including "Ansible for VMware by Examples" and "Ansible for Kubernetes by Example", and creator of the Ansible Pilot YouTube channel. He shares practical automation knowledge through tutorials, books, and video courses to help IT professionals and DevOps engineers master infrastructure automation.

Ansible Container Security: Image Scanning, Runtime Protection, and Supply Chain Security

By Luca Berton · Published 2024-01-01 · Category: installation

Automate container security with Ansible. Implement image scanning, runtime protection, Kubernetes security policies, and software supply chain security.

Introduction

Containers accelerate deployment but expand the attack surface. Vulnerable base images, misconfigured pods, and untrusted registries create security risks that manual review can't keep pace with. Ansible automates container security across the full lifecycle — from image scanning in CI/CD through runtime protection to supply chain verification.

See also: Ansible Secrets Management: Best Practices for Enterprise Credential Security

Image Scanning

Trivy Scanner Integration

---
- name: Scan container images for vulnerabilities
  hosts: localhost
  connection: local
  vars:
    images_to_scan:
      - registry.example.com/webapp:latest
      - registry.example.com/api:v2.5.0
      - registry.example.com/worker:v1.8.3
    severity_threshold: HIGH  # CRITICAL, HIGH, MEDIUM, LOW
  tasks:
    - name: Install Trivy
      ansible.builtin.shell: |
        curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
      args:
        creates: /usr/local/bin/trivy

- name: Scan images ansible.builtin.command: > trivy image --exit-code 1 --severity {{ severity_threshold }},CRITICAL --format json --output /tmp/scan-{{ item | regex_replace('[:/]', '_') }}.json {{ item }} loop: "{{ images_to_scan }}" register: scan_results failed_when: false

- name: Check for vulnerabilities ansible.builtin.set_fact: vulnerable_images: "{{ scan_results.results | selectattr('rc', 'ne', 0) | map(attribute='item') | list }}"

- name: Block deployment of vulnerable images ansible.builtin.fail: msg: | SECURITY GATE FAILED: Vulnerable images detected: {{ vulnerable_images | join('\n') }} Review scan reports in /tmp/scan-*.json when: vulnerable_images | length > 0

- name: Generate scan report ansible.builtin.template: src: scan-report.j2 dest: "/tmp/security-scan-{{ ansible_date_time.date }}.html"

CI/CD Gate

# In GitHub Actions or GitLab CI
- name: Security scan gate
  hosts: localhost
  tasks:
    - name: Scan newly built image
      ansible.builtin.command: >
        trivy image --exit-code 1
        --severity CRITICAL
        --ignore-unfixed
        {{ new_image_tag }}
      register: scan

- name: Fail pipeline if critical vulns found ansible.builtin.fail: msg: "Critical vulnerabilities found — deployment blocked" when: scan.rc != 0

Kubernetes Security Policies

Pod Security Standards

- name: Enforce Pod Security Standards
  hosts: localhost
  connection: local
  tasks:
    - name: Label namespaces with security levels
      kubernetes.core.k8s:
        state: present
        definition:
          apiVersion: v1
          kind: Namespace
          metadata:
            name: "{{ item.name }}"
            labels:
              pod-security.kubernetes.io/enforce: "{{ item.level }}"
              pod-security.kubernetes.io/warn: "{{ item.level }}"
              pod-security.kubernetes.io/audit: "{{ item.level }}"
      loop:
        - { name: production, level: restricted }
        - { name: staging, level: baseline }
        - { name: development, level: baseline }

Network Policies

    - name: Default deny all ingress
      kubernetes.core.k8s:
        state: present
        definition:
          apiVersion: networking.k8s.io/v1
          kind: NetworkPolicy
          metadata:
            name: default-deny-ingress
            namespace: production
          spec:
            podSelector: {}
            policyTypes:
              - Ingress

- name: Allow only specific traffic kubernetes.core.k8s: state: present definition: apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-webapp-to-api namespace: production spec: podSelector: matchLabels: app: api ingress: - from: - podSelector: matchLabels: app: webapp ports: - port: 8080 protocol: TCP

Security Context Enforcement

    - name: Deploy with security context
      kubernetes.core.k8s:
        state: present
        definition:
          apiVersion: apps/v1
          kind: Deployment
          metadata:
            name: secure-webapp
            namespace: production
          spec:
            replicas: 3
            selector:
              matchLabels:
                app: webapp
            template:
              metadata:
                labels:
                  app: webapp
              spec:
                securityContext:
                  runAsNonRoot: true
                  runAsUser: 1000
                  runAsGroup: 1000
                  fsGroup: 1000
                  seccompProfile:
                    type: RuntimeDefault
                containers:
                  - name: webapp
                    image: registry.example.com/webapp:v2.5.0
                    securityContext:
                      allowPrivilegeEscalation: false
                      readOnlyRootFilesystem: true
                      capabilities:
                        drop:
                          - ALL
                    resources:
                      limits:
                        cpu: 500m
                        memory: 512Mi
                      requests:
                        cpu: 250m
                        memory: 256Mi
                    volumeMounts:
                      - name: tmp
                        mountPath: /tmp
                volumes:
                  - name: tmp
                    emptyDir: {}

See also: Ansible Zero Trust Security: Implement Zero Trust Architecture for Enterprise Infrastructure

Registry Security

Private Registry with Authentication

- name: Configure container registry security
  hosts: all
  become: true
  tasks:
    - name: Create registry auth config
      ansible.builtin.template:
        src: registries.conf.j2
        dest: /etc/containers/registries.conf

- name: Block untrusted registries ansible.builtin.copy: content: | [[registry]] prefix = "docker.io" blocked = true

[[registry]] prefix = "registry.example.com" insecure = false location = "registry.example.com"

[[registry]] prefix = "quay.io" insecure = false dest: /etc/containers/registries.conf

Image Signing with Cosign

    - name: Verify image signature
      ansible.builtin.command: >
        cosign verify
        --key cosign.pub
        registry.example.com/webapp:v2.5.0
      register: verify_result
      failed_when: verify_result.rc != 0

- name: Sign image after build ansible.builtin.command: > cosign sign --key cosign.key registry.example.com/webapp:{{ new_tag }} environment: COSIGN_PASSWORD: "{{ vault_cosign_password }}" no_log: true

Runtime Security

Falco Deployment

- name: Deploy Falco runtime security
  hosts: kubernetes_nodes
  become: true
  tasks:
    - name: Deploy Falco via Helm
      kubernetes.core.helm:
        name: falco
        chart_ref: falcosecurity/falco
        release_namespace: falco-system
        create_namespace: true
        values:
          falcosidekick:
            enabled: true
            config:
              slack:
                webhookurl: "{{ slack_security_webhook }}"
          customRules:
            custom-rules.yaml: |
              - rule: Unexpected outbound connection
                desc: Detect outbound connections from production pods
                condition: >
                  outbound and container and
                  not (fd.ip in (allowed_outbound_ips))
                output: >
                  Unexpected outbound connection
                  (command=%proc.cmdline connection=%fd.name
                  container=%container.name image=%container.image.repository)
                priority: WARNING

See also: HashiCorp Vault Integration with Ansible Automation Platform: Credential Management at Scale

SBOM Generation

    - name: Generate SBOM for image
      ansible.builtin.command: >
        trivy image --format spdx-json
        --output /tmp/sbom-{{ image_name | regex_replace('[:/]', '_') }}.json
        {{ image_name }}

- name: Upload SBOM to dependency tracker ansible.builtin.uri: url: "{{ dependency_track_url }}/api/v1/bom" method: PUT headers: X-Api-Key: "{{ dependency_track_api_key }}" body_format: form-multipart body: project: "{{ project_uuid }}" bom: "{{ lookup('file', '/tmp/sbom-' + image_name | regex_replace('[:/]', '_') + '.json') }}"

Security Audit Playbook

- name: Container security audit
  hosts: all
  become: true
  tasks:
    - name: Check Docker daemon configuration
      ansible.builtin.command: docker info --format '{{ "{{" }}json .SecurityOptions{{ "}}" }}'
      register: docker_security
      changed_when: false

- name: Verify Docker uses user namespaces ansible.builtin.assert: that: "'userns' in docker_security.stdout" fail_msg: "Docker user namespace remapping not enabled"

- name: Find containers running as root ansible.builtin.shell: | docker ps -q | xargs -I {} docker inspect --format '{{ "{{" }}.Config.User{{ "}}" }} {{ "{{" }}.Name{{ "}}" }}' {} | grep -E '^\s' || true register: root_containers changed_when: false

- name: Alert on root containers ansible.builtin.debug: msg: "WARNING: Containers running as root: {{ root_containers.stdout_lines }}" when: root_containers.stdout_lines | length > 0

- name: Check for privileged containers ansible.builtin.shell: | docker ps -q | xargs -I {} docker inspect --format '{{ "{{" }}.HostConfig.Privileged{{ "}}" }} {{ "{{" }}.Name{{ "}}" }}' {} | grep true || true register: priv_containers changed_when: false

- name: Fail on privileged containers ansible.builtin.fail: msg: "CRITICAL: Privileged containers detected: {{ priv_containers.stdout_lines }}" when: priv_containers.stdout_lines | length > 0

Best Practices

Scan in CI, block in CD — Never deploy unscanned images Use distroless/minimal base images — Fewer packages = fewer vulnerabilities No root containersrunAsNonRoot: true and allowPrivilegeEscalation: false Read-only filesystem — Use readOnlyRootFilesystem: true with tmpfs for write needs Network policies everywhere — Default deny, explicit allow Sign and verify images — Cosign for supply chain integrity Generate SBOMs — Track dependencies for vulnerability notifications Runtime monitoring — Falco or similar for detecting anomalous behavior Private registries only — Block public registries in production

FAQ

How often should I scan images?

Scan on every build (CI gate) plus nightly scans of all running images — new CVEs are discovered daily against existing images.

What about vulnerability exceptions?

Use Trivy's .trivyignore file for accepted risks. Document why each exception is acceptable and set review dates.

OPA/Gatekeeper vs Pod Security Standards?

Pod Security Standards (built-in since K8s 1.25) cover common cases. OPA/Gatekeeper provides custom policies for organization-specific rules. Use both.

Conclusion

Container security automation with Ansible covers the full lifecycle — from image scanning in CI/CD through registry security, Kubernetes hardening, and runtime monitoring. By codifying security policies as Ansible playbooks, you ensure consistent enforcement across development, staging, and production environments.

Related Articles

Ansible Kubernetes GuideAnsible Zero Trust SecurityAnsible Docker Complete Guide

Category: installation

Browse all Ansible tutorials · AnsiblePilot Home