DEV Community

Cover image for πŸš€Automate Docker Image Build, Trivy Scan & Push to Docker Hub Using OpenTofu
Latchu@DevOps
Latchu@DevOps

Posted on

πŸš€Automate Docker Image Build, Trivy Scan & Push to Docker Hub Using OpenTofu

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
Enter fullscreen mode Exit fullscreen mode

1


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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

🐍 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)
Enter fullscreen mode Exit fullscreen mode

app/requirements.txt

Flask==2.2.5
Enter fullscreen mode Exit fullscreen mode

🐳 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"]

Enter fullscreen mode Exit fullscreen mode

πŸ” 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!"
Enter fullscreen mode Exit fullscreen mode

Make script executable:

chmod +x scripts/build_scan_push.sh
Enter fullscreen mode Exit fullscreen mode

🌱 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
}
Enter fullscreen mode Exit fullscreen mode

πŸ”§ 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
  }
}
Enter fullscreen mode Exit fullscreen mode

πŸ“€ Step 6 β€” Outputs

outputs.tf

output "docker_image" {
  value = "${var.image_name}:${var.image_tag}"
}
Enter fullscreen mode Exit fullscreen mode

πŸ§ͺ Step 7 β€” Run OpenTofu

cd pythonapp/tofu/
tofu init
tofu fmt
tofu validate
tofu plan
tofu apply
Enter fullscreen mode Exit fullscreen mode

2


Uploaded to Docker Hub:

latchudevops/python-secure-app:v1
Enter fullscreen mode Exit fullscreen mode

3


πŸ›‘ 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           |
Enter fullscreen mode Exit fullscreen mode

🌟 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)