ScanOps is an automated container security pipeline that builds, scans, and promotes Docker images based on their vulnerability status. Leveraging GitHub Actions (CI/CD), Trivy, AWS ECR, and S3, it ensures only safe containers are pushed to production. For vulnerability and compliance tracking, SBOMs (Software Bill of Materials) are generated and alerts are triggered on vulnerable builds.
GitHub Actions
├── Build & Push → AWS ECR (docker-dev)
└── Trivy Scan
├── If clean → ECR (docker-prod)
├── If vulnerable → ECR (docker-quarantine)
├── Generate SBOM (CycloneDX)
└── Upload Scan Results & SBOM → S3
└── Slack Alert (if vulnerable)- ECR Repos:
docker-dev: Initial stagingdocker-prod: Production-ready imagesdocker-quarantine: Vulnerable images
- Trivy Scanning: Checks for CRITICAL and HIGH vulnerabilities
- Slack Alerts: Summarized scan results for visibility
| Component | Version | Component | Version |
|---|---|---|---|
| Python | 3.11.10 | Ansible | Latest |
| AWS CLI | Latest | Trivy | 0.60.0 |
| Flask | 3.1.0 | Node.js | 20.14.0 |
| Nginx | 1.26.3 | Alpine | 3.16.0 |
- Rocky Linux VM
- Rocky Linux with
AnsibleandGitinstalled - Allocate sufficient resources: 2 CPUs, 4GB RAM
- Rocky Linux with
- AWS Account
- An AWS account with provisioned access-key and secret-key
- GitHub Account
- A GitHub account with an existing repository and GitHub Actions enabled
- SSH key configured and authorized:
- SSH key pair generated on the Rocky Linux VM
- Public key added to your GitHub account under SSH and GPG keys
- Allows passwordless
git pullandgit pushaccess
- Slack Incoming Webhook URL
- A Slack Incoming Webhook URL for sending alerts or messages
🔁 Click the “Fork” button on the top-right of the GitHub repo page to copy it to your own GitHub account. This ensures that GitHub Actions will run under your account and can access your secrets.
git clone git@github.com:<your-github-username>/scanops.git
cd scanops
ansible-galaxy install -r requirements.yamlUpdate variable file for Ansible playbook runs:
cp vars.yaml.example vars.yaml
vim vars.yaml
- Update the values in
vars.yamlwith your actual AWS and Slack credentials- Keep the sensitive file local. Be sure it's listed in
.gitignoreto avoid committing sensitive info.
Configure GitHub Secrets for CI/CD:
| Secret Name | Description |
|---|---|
| AWS_ACCOUNT_ID | Your AWS account ID |
| AWS_ACCESS_KEY_ID | IAM access key ID |
| AWS_SECRET_ACCESS_KEY | IAM secret access key |
| AWS_REGION | AWS region (e.g., us-east-1) |
| SLACK_WEBHOOK_URL | Slack webhook for notifications |
To add these:
- Go to your repository on GitHub
- Click Settings → Secrets and variables → Actions
- Click "New repository secret" and enter the name and value
- These secrets are accessed by the GitHub Actions workflow to authenticate with AWS and send alerts to Slack
ansible-playbook infra.yaml -vvThis playbook will:
- Installs system packages and dependencies
- Installs AWS CLI if not present
- Configures AWS CLI with provided credentials
- Setup ECR repositories (
docker-dev,docker-prod,docker-quarantine) - Create S3 bucket for SBOMs and scan results
Confirm Successful Execution:
ansible --version
podman --version
aws configure list
aws sts get-caller-identity
aws ecr describe-repositories --no-cli-pager
aws s3 ls- AWS CLI Configuration:
- AWS credentials are set up using a shared credentials file, and the region is configured as us-east-1
- The IAM user is verified via sts get-caller-identity, confirming its UserId, Account, and ARN
- Output confirms the three ECR repos are created:
docker-devdocker-proddocker-quarantine
- Verifies successful provisioning of S3 storage
- Confirmed in AWS web console:
- All three repositories created
- S3 storage provisioned
ansible-playbook setup_docker_apps.yaml -vvThis playbook generates:
- 4 sample apps:
- Nginx
- Alpine
- Node.js
- Python (Flask)
- Corresponding Dockerfiles and placeholder app files
Confirm Successful Execution:
cat docker/nginx/Dockerfile
cat docker/alpine/Dockerfile
cat docker/nodejs/Dockerfile
cat docker/python/Dockerfile- Output of sample Dockerfile creation from
setup_docker_apps.yamlplaybook - Confirmed four Docker apps:
- Alpine
- Nginx
- Python
- Node.js
ansible-playbook setup_pipeline.yaml -vvThis workflow runs on every push or pull request affecting files in the docker/ directory. It uses a matrix strategy to iterate over 4 Docker images: alpine, nginx, python, and nodejs.
Job 1: build-and-push
- Purpose:
- Build each Docker image and push it to the
docker-devECR repository
- Build each Docker image and push it to the
- Main Tasks:
- Checkout the repo
- Configure AWS credentials
- Log in to ECR
- Generate a timestamped image tag
- Build the Docker image
- Push the image to
docker-dev - Save the image tag for the next job
Job 2: scan-move
- Depends on:
build-and-push - Purpose:
- Scan the image with Trivy, generate an SBOM (Software Bill of Materials), and move the image based on scan results
- Main Tasks:
- Download the image tag
- Install and run Trivy scanner
- Upload scan results and SBOM to S3
- If vulnerabilities are found:
- Move image to
docker-quarantine - Send Slack alert
- Move image to
- If clean:
- Promote image to
docker-prod
- Promote image to
git add -A
git commit -m "Trigger pipeline with sample apps"
git pushThis kicks off:
- Image build & push to ECR (
docker-dev) - Trivy scan
- SBOM and scan results upload to S3
- Image promotion or quarantine based on scan
- Slack alert if
CRITICALorHIGHvulnerabilities found
- Matrix strategy kicks off 4 parallel build-and-push jobs:
- alpine
- nginx
- python
- nodejs
- Scan-move matrix is queued then completes build jobs with no errors
- SBOMs for all four sample apps were successfully generated and uploaded to the
scanops-s3bucket
- Trivy scan results were also successfully uploaded to the
scanops-s3bucket
- Trivy scan for the
nginximage returned 0 vulnerabilities, marking it as clean
- Scan for the
nodejsimage detected 374 total vulnerabilities, including 22 CRITICAL and 352 HIGH severity issues
- Trivy scan for the
alpineimage found 11 vulnerabilities, including 1 CRITICAL and 10 HIGH severity
- The
pythonimage scan revealed 129 total vulnerabilities, with 1 marked CRITICAL and 128 categorized as HIGH severity
- The
nginximage passed the Trivy scan and was successfully promoted to thedocker-prodECR repository
- The
python,nodejs, andalpineimages were pushed to thedocker-quarantineECR repository for isolation
- Slack alert triggered by ScanOps, reporting vulnerability summaries for
alpine,nodejs, andpythonimages
Building ScanOps was an awesome experience. After diving deep into docs, tweaking YAML, and troubleshooting countless CI/CD runs, seeing everything come together was worth it.
We now have a pipeline that automatically builds, scans, and sorts containers using GitHub Actions, Trivy, and AWS. Clean images go to production, vulnerable ones get quarantined—with SBOMs in S3 and Slack alerts keeping us in the loop.
It’s simple, scalable, and something I’m proud of.
Note: Run
cleanup.yamlplaybook to delete all AWS resources

















