Container Security: Falco, Trivy, and Snyk Container in Practice

Secure your containers with Falco runtime detection, Trivy image scanning, and Snyk vulnerability management. Practical examples for CI/CD pipelines and...

Y
Yash Pritwani
15 min read

The Three Pillars of Container Security

Container security operates at three levels:

<div style="margin:2.5rem auto;max-width:600px;width:100%;text-align:center;"><svg viewBox="0 0 600 220" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;"><rect width="600" height="220" rx="12" fill="#1a1a2e"/><path d="M300,25 L380,55 L380,120 Q380,170 300,195 Q220,170 220,120 L220,55 Z" fill="none" stroke="#6366f1" stroke-width="2.5"/><path d="M300,40 L365,65 L365,118 Q365,160 300,180 Q235,160 235,118 L235,65 Z" fill="#6366f1" opacity="0.15"/><rect x="280" y="95" width="40" height="30" rx="4" fill="#6366f1" opacity="0.9"/><path d="M288,95 L288,82 Q288,72 300,72 Q312,72 312,82 L312,95" fill="none" stroke="#6366f1" stroke-width="2.5"/><circle cx="300" cy="110" r="4" fill="#ffffff"/><text x="90" y="60" text-anchor="middle" fill="#3b82f6" font-size="10" font-family="system-ui">Firewall</text><line x1="130" y1="57" x2="218" y2="57" stroke="#3b82f6" stroke-width="1" stroke-dasharray="3,3"/><text x="90" y="100" text-anchor="middle" fill="#a855f7" font-size="10" font-family="system-ui">WAF</text><line x1="110" y1="97" x2="220" y2="85" stroke="#a855f7" stroke-width="1" stroke-dasharray="3,3"/><text x="90" y="140" text-anchor="middle" fill="#2dd4bf" font-size="10" font-family="system-ui">SSO / MFA</text><line x1="130" y1="137" x2="222" y2="120" stroke="#2dd4bf" stroke-width="1" stroke-dasharray="3,3"/><text x="510" y="60" text-anchor="middle" fill="#f59e0b" font-size="10" font-family="system-ui">TLS/SSL</text><line x1="470" y1="57" x2="382" y2="57" stroke="#f59e0b" stroke-width="1" stroke-dasharray="3,3"/><text x="510" y="100" text-anchor="middle" fill="#3b82f6" font-size="10" font-family="system-ui">RBAC</text><line x1="490" y1="97" x2="380" y2="85" stroke="#3b82f6" stroke-width="1" stroke-dasharray="3,3"/><text x="510" y="140" text-anchor="middle" fill="#a855f7" font-size="10" font-family="system-ui">Audit Logs</text><line x1="470" y1="137" x2="378" y2="120" stroke="#a855f7" stroke-width="1" stroke-dasharray="3,3"/></svg><p style="margin-top:0.75rem;font-size:0.85rem;color:#94a3b8;font-style:italic;line-height:1.4;">Defense in depth: multiple security layers protect your infrastructure from threats.</p></div>

1. Build time: Scan images for known vulnerabilities before deployment 2. Deploy time: Enforce policies on what can run in your environment 3. Runtime: Detect anomalous behavior in running containers

Each pillar needs different tools. Here is how Trivy, Snyk Container, and Falco cover all three.

Trivy: Image Scanning in CI/CD

Trivy by Aqua Security is the fastest and most comprehensive container image scanner. It detects vulnerabilities in OS packages, language-specific packages, misconfigurations, and secrets.

# Scan a Docker image
trivy image python:3.12-slim

# Output (abbreviated):
# python:3.12-slim (debian 12.5)
# Total: 45 (UNKNOWN: 0, LOW: 25, MEDIUM: 15, HIGH: 4, CRITICAL: 1)
#
# +-----------+------------------+----------+-------------------+
# | Library   | Vulnerability    | Severity | Fixed Version     |
# +-----------+------------------+----------+-------------------+
# | libexpat  | CVE-2024-50602   | CRITICAL | 2.5.0-1+deb12u2   |
# | openssl   | CVE-2024-9143    | HIGH     | 3.0.15-1~deb12u1  |
# +-----------+------------------+----------+-------------------+

Trivy in a Gitea Actions CI pipeline:

# .gitea/workflows/security-scan.yml
name: Security Scan
on: [push, pull_request]

jobs:
  trivy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build image
        run: docker build -t my-app:scan .

      - name: Run Trivy vulnerability scanner
        run: |
          trivy image --exit-code 1 --severity CRITICAL,HIGH \
            --ignore-unfixed \
            --format table \
            my-app:scan

      - name: Run Trivy config scanner
        run: |
          trivy config --exit-code 1 --severity HIGH,CRITICAL .

      - name: Run Trivy secret scanner
        run: |
          trivy fs --scanners secret --exit-code 1 .

Trivy Dockerfile scanning catches misconfigurations:

trivy config Dockerfile

# Findings:
# - DS002: Image user should not be 'root' (HIGH)
# - DS026: No HEALTHCHECK defined (LOW)
# - DS001: ':latest' tag used (MEDIUM)

Snyk Container: Developer-Friendly Vulnerability Management

Snyk Container focuses on the developer workflow. It integrates into your IDE, Git repository, and CI/CD pipeline to catch vulnerabilities early and suggest fixes.

# Scan image with Snyk
snyk container test python:3.12-slim --severity-threshold=high

# Monitor image for new vulnerabilities
snyk container monitor python:3.12-slim --org=techsaas

# Test a Dockerfile before building
snyk container test --file=Dockerfile .

What makes Snyk different from Trivy:

# Snyk suggests base image upgrades
snyk container test my-app:latest

# Output includes:
# Tested 125 dependencies for known issues
# Found 12 issues (3 critical, 4 high, 5 medium)
#
# Base Image Recommendations:
# Current: python:3.12-slim (45 vulnerabilities)
# Recommended: python:3.12-slim-bookworm (12 vulnerabilities)
# Alternative: python:3.12-alpine (3 vulnerabilities)

Snyk proactively tells you which base image has fewer vulnerabilities.

<div style="margin:2.5rem auto;max-width:600px;width:100%;text-align:center;"><svg viewBox="0 0 600 180" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;"><rect width="600" height="180" rx="12" fill="#1a1a2e"/><circle cx="60" cy="90" r="20" fill="none" stroke="#3b82f6" stroke-width="2"/><text x="60" y="94" text-anchor="middle" fill="#3b82f6" font-size="11" font-family="system-ui">User</text><rect x="120" y="65" width="95" height="50" rx="8" fill="#6366f1" opacity="0.85"/><text x="167" y="85" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">Identity</text><text x="167" y="100" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">Verify</text><rect x="250" y="65" width="95" height="50" rx="8" fill="#a855f7" opacity="0.85"/><text x="297" y="85" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">Policy</text><text x="297" y="100" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">Engine</text><rect x="380" y="65" width="95" height="50" rx="8" fill="#2dd4bf" opacity="0.85"/><text x="427" y="85" text-anchor="middle" fill="#1a1a2e" font-size="10" font-family="system-ui">Access</text><text x="427" y="100" text-anchor="middle" fill="#1a1a2e" font-size="10" font-family="system-ui">Proxy</text><rect x="510" y="65" width="60" height="50" rx="8" fill="#f59e0b" opacity="0.85"/><text x="540" y="94" text-anchor="middle" fill="#1a1a2e" font-size="10" font-family="system-ui">App</text><defs><marker id="arrow5" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6" fill="#e2e8f0"/></marker></defs><line x1="82" y1="90" x2="118" y2="90" stroke="#e2e8f0" stroke-width="1.5" marker-end="url(#arrow5)"/><line x1="217" y1="90" x2="248" y2="90" stroke="#e2e8f0" stroke-width="1.5" marker-end="url(#arrow5)"/><line x1="347" y1="90" x2="378" y2="90" stroke="#e2e8f0" stroke-width="1.5" marker-end="url(#arrow5)"/><line x1="477" y1="90" x2="508" y2="90" stroke="#e2e8f0" stroke-width="1.5" marker-end="url(#arrow5)"/><text x="167" y="140" text-anchor="middle" fill="#94a3b8" font-size="9" font-family="system-ui">MFA + Device</text><text x="297" y="140" text-anchor="middle" fill="#94a3b8" font-size="9" font-family="system-ui">Least Privilege</text><text x="427" y="140" text-anchor="middle" fill="#94a3b8" font-size="9" font-family="system-ui">Encrypted Tunnel</text><text x="300" y="165" text-anchor="middle" fill="#6366f1" font-size="11" font-family="system-ui" font-weight="bold">Never Trust, Always Verify</text></svg><p style="margin-top:0.75rem;font-size:0.85rem;color:#94a3b8;font-style:italic;line-height:1.4;">Zero Trust architecture: every request is verified through identity, policy, and access proxy layers.</p></div>

Trivy vs Snyk: Comparison

Feature
Trivy
Snyk Container

|---------|-------|----------------|

License
Apache 2.0 (free)
Freemium (200 tests/month free)
Scan speed
Very fast (local DB)
Fast (cloud API)
OS vuln detection
Excellent
Excellent
Language packages
Excellent
Excellent
Base image recommendations
No
Yes
Fix pull requests
No
Yes (auto PRs)
IDE integration
VS Code extension
VS Code, IntelliJ, vim
Dockerfile scanning
Yes
Yes
Secret detection
Yes
Yes
SBOM generation
Yes (CycloneDX, SPDX)
Yes
IaC scanning
Yes (Terraform, K8s)
Yes
Offline scanning
Yes
No (needs API)
CI integration
Any (CLI)
GitHub, GitLab, Bitbucket, Jenkins

Our recommendation: Use Trivy in CI/CD (free, fast, offline-capable) and Snyk for developer workflow (IDE integration, base image recommendations, auto-fix PRs).

Falco: Runtime Security Detection

Falco is a CNCF project that detects anomalous behavior in running containers using kernel-level syscall monitoring. Think of it as an intrusion detection system for containers.

# Falco deployment with Docker Compose
services:
  falco:
    image: falcosecurity/falco:latest
    container_name: falco
    privileged: true
    volumes:
      - /var/run/docker.sock:/host/var/run/docker.sock:ro
      - /proc:/host/proc:ro
      - /etc:/host/etc:ro
      - ./falco/rules:/etc/falco/rules.d:ro
    environment:
      - FALCO_BPF_PROBE=""
    mem_limit: 256m

Custom Falco rules:

# falco/rules/custom-rules.yaml

# Detect shell spawned in a container
- rule: Shell Spawned in Container
  desc: Detect shell execution in a running container
  condition: >
    spawned_process and container and
    proc.name in (bash, sh, zsh, dash, ash) and
    not proc.pname in (cron, containerd-shim)
  output: >
    Shell spawned in container
    (user=%user.name container=%container.name shell=%proc.name
     parent=%proc.pname cmdline=%proc.cmdline image=%container.image.repository)
  priority: WARNING
  tags: [container, shell]

# Detect sensitive file access
- rule: Read Sensitive File in Container
  desc: Detect reading of sensitive files like /etc/shadow
  condition: >
    open_read and container and
    fd.name in (/etc/shadow, /etc/passwd, /etc/sudoers) and
    not proc.name in (sshd, login, su, sudo)
  output: >
    Sensitive file read in container
    (user=%user.name file=%fd.name container=%container.name image=%container.image.repository)
  priority: ERROR
  tags: [container, filesystem]

# Detect outbound connection to unexpected port
- rule: Unexpected Outbound Connection
  desc: Detect containers making outbound connections to non-standard ports
  condition: >
    outbound and container and
    not fd.sport in (80, 443, 5432, 6379, 27017, 53, 8080, 3000) and
    not container.name in (cloudflared, traefik)
  output: >
    Unexpected outbound connection from container
    (container=%container.name image=%container.image.repository
     connection=%fd.name port=%fd.sport)
  priority: NOTICE
  tags: [container, network]

Building a Complete Security Pipeline

Here is the full container security pipeline we implement at TechSaaS:

Developer writes code
        ↓
IDE: Snyk plugin warns about vulnerable dependencies
        ↓
Git push triggers CI pipeline
        ↓
CI Step 1: Trivy scans Dockerfile for misconfigurations
CI Step 2: Docker build
CI Step 3: Trivy scans built image for vulnerabilities
CI Step 4: Trivy scans for leaked secrets
CI Step 5: Block deployment if CRITICAL/HIGH found
        ↓
Deploy to production
        ↓
Runtime: Falco monitors syscalls for anomalous behavior
Runtime: CrowdSec monitors network for attacks
        ↓
Alert: Ntfy notification → Investigate → Remediate

<div style="margin:2.5rem auto;max-width:600px;width:100%;text-align:center;"><svg viewBox="0 0 600 200" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;"><rect width="600" height="200" rx="12" fill="#1a1a2e"/><text x="80" y="25" text-anchor="middle" fill="#94a3b8" font-size="10" font-family="system-ui">Input</text><circle cx="80" cy="50" r="14" fill="none" stroke="#3b82f6" stroke-width="2"/><circle cx="80" cy="100" r="14" fill="none" stroke="#3b82f6" stroke-width="2"/><circle cx="80" cy="150" r="14" fill="none" stroke="#3b82f6" stroke-width="2"/><text x="230" y="25" text-anchor="middle" fill="#94a3b8" font-size="10" font-family="system-ui">Hidden</text><circle cx="230" cy="45" r="14" fill="#6366f1" opacity="0.8"/><circle cx="230" cy="85" r="14" fill="#6366f1" opacity="0.8"/><circle cx="230" cy="125" r="14" fill="#6366f1" opacity="0.8"/><circle cx="230" cy="165" r="14" fill="#6366f1" opacity="0.8"/><text x="380" y="25" text-anchor="middle" fill="#94a3b8" font-size="10" font-family="system-ui">Hidden</text><circle cx="380" cy="55" r="14" fill="#a855f7" opacity="0.8"/><circle cx="380" cy="100" r="14" fill="#a855f7" opacity="0.8"/><circle cx="380" cy="145" r="14" fill="#a855f7" opacity="0.8"/><text x="520" y="25" text-anchor="middle" fill="#94a3b8" font-size="10" font-family="system-ui">Output</text><circle cx="520" cy="80" r="14" fill="none" stroke="#2dd4bf" stroke-width="2"/><circle cx="520" cy="130" r="14" fill="none" stroke="#2dd4bf" stroke-width="2"/><line x1="94" y1="50" x2="216" y2="45" stroke="#e2e8f0" stroke-width="0.5" opacity="0.3"/><line x1="94" y1="50" x2="216" y2="85" stroke="#e2e8f0" stroke-width="0.5" opacity="0.3"/><line x1="94" y1="50" x2="216" y2="125" stroke="#e2e8f0" stroke-width="0.5" opacity="0.3"/><line x1="94" y1="50" x2="216" y2="165" stroke="#e2e8f0" stroke-width="0.5" opacity="0.3"/><line x1="94" y1="100" x2="216" y2="45" stroke="#e2e8f0" stroke-width="0.5" opacity="0.3"/><line x1="94" y1="100" x2="216" y2="85" stroke="#e2e8f0" stroke-width="0.5" opacity="0.3"/><line x1="94" y1="100" x2="216" y2="125" stroke="#e2e8f0" stroke-width="0.5" opacity="0.3"/><line x1="94" y1="100" x2="216" y2="165" stroke="#e2e8f0" stroke-width="0.5" opacity="0.3"/><line x1="94" y1="150" x2="216" y2="45" stroke="#e2e8f0" stroke-width="0.5" opacity="0.3"/><line x1="94" y1="150" x2="216" y2="85" stroke="#e2e8f0" stroke-width="0.5" opacity="0.3"/><line x1="94" y1="150" x2="216" y2="125" stroke="#e2e8f0" stroke-width="0.5" opacity="0.3"/><line x1="94" y1="150" x2="216" y2="165" stroke="#e2e8f0" stroke-width="0.5" opacity="0.3"/><line x1="244" y1="45" x2="366" y2="55" stroke="#e2e8f0" stroke-width="0.5" opacity="0.3"/><line x1="244" y1="45" x2="366" y2="100" stroke="#e2e8f0" stroke-width="0.5" opacity="0.3"/><line x1="244" y1="45" x2="366" y2="145" stroke="#e2e8f0" stroke-width="0.5" opacity="0.3"/><line x1="244" y1="85" x2="366" y2="55" stroke="#e2e8f0" stroke-width="0.5" opacity="0.3"/><line x1="244" y1="85" x2="366" y2="100" stroke="#e2e8f0" stroke-width="0.5" opacity="0.3"/><line x1="244" y1="85" x2="366" y2="145" stroke="#e2e8f0" stroke-width="0.5" opacity="0.3"/><line x1="244" y1="125" x2="366" y2="55" stroke="#e2e8f0" stroke-width="0.5" opacity="0.3"/><line x1="244" y1="125" x2="366" y2="100" stroke="#e2e8f0" stroke-width="0.5" opacity="0.3"/><line x1="244" y1="125" x2="366" y2="145" stroke="#e2e8f0" stroke-width="0.5" opacity="0.3"/><line x1="244" y1="165" x2="366" y2="55" stroke="#e2e8f0" stroke-width="0.5" opacity="0.3"/><line x1="244" y1="165" x2="366" y2="100" stroke="#e2e8f0" stroke-width="0.5" opacity="0.3"/><line x1="244" y1="165" x2="366" y2="145" stroke="#e2e8f0" stroke-width="0.5" opacity="0.3"/><line x1="394" y1="55" x2="506" y2="80" stroke="#e2e8f0" stroke-width="0.5" opacity="0.3"/><line x1="394" y1="55" x2="506" y2="130" stroke="#e2e8f0" stroke-width="0.5" opacity="0.3"/><line x1="394" y1="100" x2="506" y2="80" stroke="#e2e8f0" stroke-width="0.5" opacity="0.3"/><line x1="394" y1="100" x2="506" y2="130" stroke="#e2e8f0" stroke-width="0.5" opacity="0.3"/><line x1="394" y1="145" x2="506" y2="80" stroke="#e2e8f0" stroke-width="0.5" opacity="0.3"/><line x1="394" y1="145" x2="506" y2="130" stroke="#e2e8f0" stroke-width="0.5" opacity="0.3"/></svg><p style="margin-top:0.75rem;font-size:0.85rem;color:#94a3b8;font-style:italic;line-height:1.4;">Neural network architecture: data flows through input, hidden, and output layers.</p></div>

Practical Dockerfile Hardening

Apply these fixes before scanning catches them:

# BAD: Running as root, using latest tag
FROM python:latest
COPY . /app
RUN pip install -r requirements.txt
CMD ["python", "app.py"]

# GOOD: Non-root user, pinned version, multi-stage, healthcheck
FROM python:3.12-slim-bookworm AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --user -r requirements.txt

FROM python:3.12-slim-bookworm
RUN groupadd -r appuser && useradd -r -g appuser appuser
WORKDIR /app
COPY --from=builder /root/.local /home/appuser/.local
COPY --chown=appuser:appuser . .
ENV PATH=/home/appuser/.local/bin:$PATH
USER appuser
HEALTHCHECK --interval=30s --timeout=5s \
  CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8080/health')"
EXPOSE 8080
CMD ["python", "app.py"]

Container security is not optional — it is a continuous process. Start with Trivy in your CI pipeline (it takes 5 minutes to set up), then add Falco for runtime detection, and use Snyk when you want developer-friendly vulnerability management. At TechSaaS, we run Trivy CI scans on all our Gitea repositories and CrowdSec for runtime intrusion prevention.

#container-security#falco#trivy#snyk#docker#devsecops

Need help with security?

TechSaaS provides expert consulting and managed services for cloud infrastructure, DevOps, and AI/ML operations.