In this article, I'll walk you through a complete real-time DevOps automation workflow:
β Build a Docker image from a Python application
β Scan the image using Trivy (HIGH+CRITICAL severity)
β Save scan report to /home/ubuntu/trivy-report.txt
β If scan passes β tag & push image to Docker Hub
β If scan fails β block push
β Entire workflow automated using OpenTofu (null_resource + local-exec)
This is a real-time DevOps automation task, similar to what you would implement in CI pipelines β but fully executed using OpenTofu on a server.
π Project Structure
pythonapp/
βββ tofu
βββ Dockerfile
βββ app
β βββ app.py
β βββ requirements.txt
βββ main.tf
βββ outputs.tf
βββ scripts
β βββ build_scan_push.sh
βββ terraform.tfstate
βββ terraform.tfstate.backup
βββ variables.tf
Pre-req:
Install the Docker
# Add Docker's official GPG key:
sudo apt update
sudo apt install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
# Add the repository to Apt sources:
sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Signed-By: /etc/apt/keyrings/docker.asc
EOF
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl start docker
Install Trivy
sudo apt-get install wget gnupg
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | gpg --dearmor | sudo tee /usr/share/keyrings/trivy.gpg > /dev/null
echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] https://aquasecurity.github.io/trivy-repo/deb generic main" | sudo tee -a /etc/apt/sources.list.d/trivy.list
sudo apt-get update
sudo apt-get install trivy
π Step 1 β Minimal Python Application
app/app.py
from flask import Flask
app = Flask(__name__)
@app.route("/")
def home():
return "Hello from Secure Python App!"
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
app/requirements.txt
Flask==2.2.5
π³ Step 2 β Dockerfile for Python Application
Dockerfile
FROM python:3.10-slim
WORKDIR /app
COPY app/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app/ .
EXPOSE 5000
CMD ["python", "app.py"]
π Step 3 β Trivy + Build + Scan + Push Script
scripts/build_scan_push.sh
#!/bin/bash
IMAGE_NAME=$1
IMAGE_TAG=$2
DOCKER_USER=$3
DOCKER_PASS=$4
REPORT_PATH="/home/ubuntu/trivy-report.txt"
echo "=== Building Docker image ==="
docker build -t "${IMAGE_NAME}:${IMAGE_TAG}" .
echo "=== Running Trivy scan ==="
trivy image --exit-code 1 --severity HIGH,CRITICAL "${IMAGE_NAME}:${IMAGE_TAG}" > "$REPORT_PATH" 2>&1
SCAN_STATUS=$?
echo "=== Scan report stored at $REPORT_PATH ==="
if [ $SCAN_STATUS -ne 0 ]; then
echo "β Trivy scan failed β image NOT pushed!"
exit 1
fi
echo "=== Logging in to Docker Hub ==="
echo "$DOCKER_PASS" | docker login -u "$DOCKER_USER" --password-stdin
echo "=== Tagging image ==="
docker tag "${IMAGE_NAME}:${IMAGE_TAG}" "${DOCKER_USER}/${IMAGE_NAME}:${IMAGE_TAG}"
echo "=== Pushing image ==="
docker push "${DOCKER_USER}/${IMAGE_NAME}:${IMAGE_TAG}"
echo "β Scan passed β image pushed successfully!"
Make script executable:
chmod +x scripts/build_scan_push.sh
π± Step 4 β OpenTofu Variables
variables.tf
variable "image_name" {
type = string
}
variable "image_tag" {
type = string
}
variable "docker_username" {
type = string
}
variable "docker_password" {
type = string
sensitive = true
}
π§ Step 5 β Main OpenTofu Configuration
main.tf
terraform {
required_providers {
docker = {
source = "kreuzwerker/docker"
version = "~> 3.0"
}
}
}
resource "null_resource" "build_scan_push" {
provisioner "local-exec" {
command = <<EOT
bash ./scripts/build_scan_push.sh \
${var.image_name} \
${var.tag} \
${var.dockerhub_user} \
${var.dockerhub_pass}
EOT
}
}
π€ Step 6 β Outputs
outputs.tf
output "docker_image" {
value = "${var.image_name}:${var.image_tag}"
}
π§ͺ Step 7 β Run OpenTofu
cd pythonapp/tofu/
tofu init
tofu fmt
tofu validate
tofu plan
tofu apply
Uploaded to Docker Hub:
latchudevops/python-secure-app:v1
π‘ Why This Workflow Is Production-Ready
| Step | Purpose |
| ------------------- | ----------------------------------- |
| Docker Build | Standard packaging of application |
| Trivy Scan | Security scanning (HIGH + CRITICAL) |
| Fail-Fast Push | Prevent vulnerable images |
| OpenTofu Automation | IaC-driven repeatable workflow |
| Sensitive Variables | Credentials kept securely |
π Thanks for reading! If this post added value, a like β€οΈ, follow, or share would encourage me to keep creating more content.
β Latchu | Senior DevOps & Cloud Engineer
βοΈ AWS | GCP | βΈοΈ Kubernetes | π Security | β‘ Automation
π Sharing hands-on guides, best practices & real-world cloud solutions



Top comments (0)