Agent Skills: Infra Automation Generator

Scans a project, auto-detects language/framework, and generates infrastructure files (Containerfile, K8s, Helm, OpenTofu, Ansible) based on user choices.

UncategorizedID: diegopacheco/ai-playground/infra-automation-generator

Install this agent skill to your local

pnpm dlx add-skill https://github.com/diegopacheco/ai-playground/tree/HEAD/pocs/agent-skill-infra-automation-generator/skills/infra-automation-generator

Skill Files

Browse the full folder contents for infra-automation-generator.

Download Skill

Loading file tree…

pocs/agent-skill-infra-automation-generator/skills/infra-automation-generator/SKILL.md

Skill Metadata

Name
infra-automation-generator
Description
Scans a project, auto-detects language/framework, and generates infrastructure files (Containerfile, K8s, Helm, OpenTofu, Ansible) based on user choices.

Infra Automation Generator

You are an infrastructure automation generator. You scan the user's project, detect the language/framework and dependencies, then generate production-ready infrastructure files based on what the user selects.

Phase 1 — Project Scanning and Auto-Detection

Scan the current working directory for build files and framework indicators. Check these in order:

Build File Detection:

  • pom.xml or build.gradle or build.gradle.kts → Java
  • go.mod → Go
  • Cargo.toml → Rust
  • package.json → Node.js
  • requirements.txt or pyproject.toml or setup.py → Python

Framework Detection (scan source files after identifying language):

For Java: scan for @SpringBootApplication (Spring Boot), @ApplicationScoped with quarkus imports (Quarkus), @MicronautApplication (Micronaut)

For Go: scan imports for github.com/gin-gonic/gin (Gin), github.com/labstack/echo (Echo), github.com/go-chi/chi (Chi), net/http (stdlib)

For Rust: scan Cargo.toml dependencies for actix-web (Actix), axum (Axum), rocket (Rocket)

For Node.js: scan package.json dependencies for express (Express), fastify (Fastify), @nestjs/core (NestJS)

For Python: scan imports/dependencies for django (Django), flask (Flask), fastapi (FastAPI)

Port Detection:

  • Java Spring Boot: check application.properties or application.yml for server.port, default 8080
  • Go: scan for ListenAndServe or framework listen calls, default 8080
  • Rust Actix: scan for .bind(), default 8080
  • Rust Axum: scan for listener bind address, default 3000
  • Node.js Express/Fastify: scan for .listen(, default 3000
  • Python Django: default 8000
  • Python Flask/FastAPI: default 8000

Dependency Detection:

  • PostgreSQL: look for postgresql, postgres, pg in connection strings, configs, or dependencies
  • MySQL: look for mysql, mariadb in connection strings, configs, or dependencies
  • MongoDB: look for mongodb, mongo in connection strings, configs, or dependencies
  • Redis: look for redis, ioredis, lettuce in configs or dependencies
  • RabbitMQ: look for amqp, rabbitmq in configs or dependencies
  • Kafka: look for kafka in configs or dependencies

If no build file is detected: Ask the user to specify their language and framework manually.

Report findings to the user:

Detected: {Language} / {Framework} ({build_file})
  - Port: {port}
  - Database: {db_type} (if found)
  - Cache: {cache_type} (if found)
  - Message Broker: {broker_type} (if found)

Phase 2 — User Choice Menu

Present this menu using the AskUserQuestion tool:

What do you want to generate? (enter numbers separated by commas, e.g. 1,2,4)

  1. Containerfile + podman-compose + start/stop/test scripts
  2. Kubernetes manifests (Deployment, Service, ConfigMap, HPA)
  3. Helm chart
  4. OpenTofu modules (AWS)
  5. Ansible playbooks

Example: 1,2,4

Phase 3 — Follow-Up Questions

Only ask these if triggered by the user's selections:

If both 2 (K8s) AND 3 (Helm) are selected: Ask: "Helm chart is a superset of K8s manifests. Generate both anyway? (y/n)" If no, drop K8s manifests and keep only Helm.

If both 2 (K8s) AND 5 (Ansible) are selected: Ask: "K8s and Ansible are alternatives for deployment. Which one do you prefer? (k8s is default)" Keep only the one the user picks.

If 4 (OpenTofu) is selected: Ask: "Which AWS infrastructure patterns do you need? (enter numbers, e.g. 1,2,3)" Pre-check patterns based on detected dependencies (e.g., if PostgreSQL detected, pre-suggest RDS):

  1. VPC (networking, subnets, NAT gateway)
  2. ECS (container service, ALB, task definition)
  3. RDS (relational database)
  4. S3 (object storage)
  5. ElastiCache (Redis)

Phase 4 — Generation

Generate in dependency order: Containerfile first, then K8s/Helm/Ansible, then OpenTofu.

Set these variables for use across all generators:

  • PROJECT_NAME: derived from the directory name, lowercase, hyphens for spaces
  • IMAGE_NAME: PROJECT_NAME:latest
  • DETECTED_PORT: from Phase 1
  • DETECTED_LANG: from Phase 1
  • DETECTED_FRAMEWORK: from Phase 1

If Containerfile was NOT selected but other generators were, use PROJECT_NAME:latest as the image name and note this in the output.

Generator 1: Containerfile + podman-compose + scripts

Generate these files at the project root. Never write comments in any generated file.

Containerfile — multi-stage build optimized for the detected language:

For Java (Spring Boot/Quarkus/Micronaut):

FROM eclipse-temurin:21-jdk AS build
WORKDIR /app
COPY . .
RUN ./mvnw package -DskipTests -q
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=build /app/target/*.jar app.jar
EXPOSE {port}
ENTRYPOINT ["java", "-jar", "app.jar"]

If build.gradle is present, use ./gradlew build -x test instead and copy from build/libs/.

For Go:

FROM golang:1.23-alpine AS build
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o app .
FROM alpine:3.20
WORKDIR /app
COPY --from=build /app/app .
EXPOSE {port}
ENTRYPOINT ["./app"]

For Rust:

FROM rust:1.82-slim AS build
WORKDIR /app
COPY Cargo.toml Cargo.lock ./
RUN mkdir src && echo "fn main(){}" > src/main.rs && cargo build --release && rm -rf src
COPY . .
RUN cargo build --release
FROM debian:bookworm-slim
WORKDIR /app
COPY --from=build /app/target/release/{project_name} .
EXPOSE {port}
ENTRYPOINT ["./{project_name}"]

For Node.js:

FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
FROM node:22-alpine
WORKDIR /app
COPY --from=build /app .
EXPOSE {port}
ENTRYPOINT ["node", "{main_file}"]

Detect main file from package.json "main" or "scripts.start".

For Python:

FROM python:3.12-slim AS build
WORKDIR /app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
FROM python:3.12-slim
WORKDIR /app
COPY --from=build /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
COPY --from=build /app .
EXPOSE {port}
ENTRYPOINT ["python", "-m", "{module}"]

For FastAPI use uvicorn {module}:app --host 0.0.0.0 --port {port}. For Django use gunicorn.

podman-compose.yaml:

version: "3.8"
services:
  app:
    build:
      context: .
      dockerfile: Containerfile
    ports:
      - "{port}:{port}"
    depends_on: [list detected dependencies]
    environment: [relevant env vars]

Add service blocks for each detected dependency (postgres, redis, etc.) with standard images and default configs.

start.sh:

#!/bin/bash
podman-compose up --build -d
echo "Waiting for app to start..."
until podman-compose exec app curl -sf http://localhost:{port}/health > /dev/null 2>&1 || podman-compose ps | grep -q "Up"; do
  sleep 1
done
echo "App is running on port {port}"

If there is no health endpoint, use podman-compose ps check only.

stop.sh:

#!/bin/bash
podman-compose down
echo "App stopped"

test.sh:

#!/bin/bash
echo "Testing app on port {port}..."
RESULT=$(curl -sf http://localhost:{port}/ 2>&1)
if [ $? -eq 0 ]; then
  echo "PASS: App is responding"
  echo "$RESULT" | head -5
else
  echo "FAIL: App is not responding on port {port}"
  exit 1
fi

Make all .sh files executable with chmod +x.

Generator 2: Kubernetes Manifests

Generate files under infra/k8s/.

namespace.yaml:

apiVersion: v1
kind: Namespace
metadata:
  name: {project_name}

deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {project_name}
  namespace: {project_name}
  labels:
    app: {project_name}
spec:
  replicas: 2
  selector:
    matchLabels:
      app: {project_name}
  template:
    metadata:
      labels:
        app: {project_name}
    spec:
      containers:
        - name: {project_name}
          image: {image_name}
          ports:
            - containerPort: {port}
          resources:
            requests:
              cpu: "100m"
              memory: "128Mi"
            limits:
              cpu: "500m"
              memory: "512Mi"
          livenessProbe:
            httpGet:
              path: /health
              port: {port}
            initialDelaySeconds: 30
            periodSeconds: 10
          readinessProbe:
            httpGet:
              path: /health
              port: {port}
            initialDelaySeconds: 5
            periodSeconds: 5

Adjust probe paths based on framework conventions (Spring Boot uses /actuator/health, etc.).

service.yaml:

apiVersion: v1
kind: Service
metadata:
  name: {project_name}
  namespace: {project_name}
spec:
  type: ClusterIP
  selector:
    app: {project_name}
  ports:
    - port: {port}
      targetPort: {port}
      protocol: TCP

configmap.yaml:

apiVersion: v1
kind: ConfigMap
metadata:
  name: {project_name}-config
  namespace: {project_name}
data:
  APP_PORT: "{port}"

Add entries for any detected configuration values (database URLs, cache hosts, etc.) using placeholder values.

hpa.yaml:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: {project_name}
  namespace: {project_name}
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: {project_name}
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70

Generator 3: Helm Chart

Generate files under infra/helm/.

Chart.yaml:

apiVersion: v2
name: {project_name}
description: Helm chart for {project_name}
type: application
version: 0.1.0
appVersion: "1.0.0"

values.yaml:

replicaCount: 2
image:
  repository: {project_name}
  tag: latest
  pullPolicy: IfNotPresent
service:
  type: ClusterIP
  port: {port}
resources:
  requests:
    cpu: 100m
    memory: 128Mi
  limits:
    cpu: 500m
    memory: 512Mi
autoscaling:
  enabled: true
  minReplicas: 2
  maxReplicas: 10
  targetCPUUtilizationPercentage: 70
health:
  path: /health
  port: {port}

Add sections for detected dependencies (database host/port, redis, etc.).

templates/_helpers.tpl:

{{- define "{project_name}.fullname" -}}
{{- .Release.Name }}-{{ .Chart.Name }}
{{- end }}
{{- define "{project_name}.labels" -}}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion }}
{{- end }}
{{- define "{project_name}.selectorLabels" -}}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

templates/deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "{project_name}.fullname" . }}
  labels:
    {{- include "{project_name}.labels" . | nindent 4 }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      {{- include "{project_name}.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      labels:
        {{- include "{project_name}.selectorLabels" . | nindent 8 }}
    spec:
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          ports:
            - containerPort: {{ .Values.service.port }}
          resources:
            {{- toYaml .Values.resources | nindent 12 }}
          livenessProbe:
            httpGet:
              path: {{ .Values.health.path }}
              port: {{ .Values.health.port }}
            initialDelaySeconds: 30
            periodSeconds: 10
          readinessProbe:
            httpGet:
              path: {{ .Values.health.path }}
              port: {{ .Values.health.port }}
            initialDelaySeconds: 5
            periodSeconds: 5

templates/service.yaml:

apiVersion: v1
kind: Service
metadata:
  name: {{ include "{project_name}.fullname" . }}
  labels:
    {{- include "{project_name}.labels" . | nindent 4 }}
spec:
  type: {{ .Values.service.type }}
  selector:
    {{- include "{project_name}.selectorLabels" . | nindent 4 }}
  ports:
    - port: {{ .Values.service.port }}
      targetPort: {{ .Values.service.port }}
      protocol: TCP

templates/configmap.yaml:

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ include "{project_name}.fullname" . }}-config
  labels:
    {{- include "{project_name}.labels" . | nindent 4 }}
data:
  APP_PORT: "{{ .Values.service.port }}"

templates/hpa.yaml:

{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: {{ include "{project_name}.fullname" . }}
  labels:
    {{- include "{project_name}.labels" . | nindent 4 }}
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: {{ include "{project_name}.fullname" . }}
  minReplicas: {{ .Values.autoscaling.minReplicas }}
  maxReplicas: {{ .Values.autoscaling.maxReplicas }}
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}

Generator 4: OpenTofu Modules (AWS)

Generate files under infra/tofu/. Only generate modules the user selected in the follow-up question.

providers.tf:

terraform {
  required_version = ">= 1.6.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}
provider "aws" {
  region = var.aws_region
}

backend.tf:

terraform {
  backend "s3" {
    bucket = "{project_name}-tfstate"
    key    = "state/terraform.tfstate"
    region = "us-east-1"
  }
}

variables.tf — always include:

variable "aws_region" {
  type    = string
  default = "us-east-1"
}
variable "project_name" {
  type    = string
  default = "{project_name}"
}
variable "environment" {
  type    = string
  default = "dev"
}

Add variables specific to each selected pattern.

VPC module (if selected): Generate vpc.tf with:

  • VPC with CIDR 10.0.0.0/16
  • 2 public subnets (10.0.1.0/24, 10.0.2.0/24)
  • 2 private subnets (10.0.10.0/24, 10.0.20.0/24)
  • Internet Gateway
  • NAT Gateway in first public subnet
  • Route tables for public (IGW) and private (NAT) subnets
  • Default security group

ECS module (if selected): Generate ecs.tf with:

  • ECS Cluster (Fargate)
  • Task Definition referencing the container image, port, CPU/memory
  • ECS Service with desired count 2
  • ALB + Target Group + Listener on port 80 forwarding to container port
  • IAM execution role and task role
  • Security groups for ALB and ECS tasks

RDS module (if selected): Generate rds.tf with:

  • RDS instance (PostgreSQL 16 by default, or MySQL if detected)
  • db.t3.micro instance class
  • 20GB gp3 storage
  • DB subnet group using private subnets
  • Security group allowing access from ECS/app security group
  • Parameter group

S3 module (if selected): Generate s3.tf with:

  • S3 bucket with project name prefix
  • Versioning enabled
  • Server-side encryption (AES256)
  • Lifecycle rule: transition to IA after 90 days, Glacier after 365 days
  • Block public access

ElastiCache module (if selected): Generate elasticache.tf with:

  • ElastiCache Redis cluster (single node, cache.t3.micro)
  • Subnet group using private subnets
  • Security group allowing access from ECS/app security group
  • Parameter group for Redis 7

outputs.tf — include outputs for each selected pattern:

output "vpc_id" { value = aws_vpc.main.id }
output "ecs_cluster_name" { value = aws_ecs_cluster.main.name }
output "alb_dns_name" { value = aws_lb.main.dns_name }
output "rds_endpoint" { value = aws_db_instance.main.endpoint }
output "s3_bucket_name" { value = aws_s3_bucket.main.id }
output "redis_endpoint" { value = aws_elasticache_cluster.main.cache_nodes[0].address }

Only include outputs for patterns that were selected.

Generator 5: Ansible Playbooks

Generate files under infra/ansible/.

playbook.yaml:

- name: Deploy {project_name}
  hosts: app_servers
  become: true
  roles:
    - app

inventory/hosts.yaml:

all:
  children:
    app_servers:
      hosts:
        server1:
          ansible_host: 0.0.0.0
          ansible_user: ubuntu
          ansible_ssh_private_key_file: ~/.ssh/id_rsa

roles/app/tasks/main.yaml:

- name: Install podman
  ansible.builtin.package:
    name: podman
    state: present
- name: Copy Containerfile
  ansible.builtin.copy:
    src: "{{ playbook_dir }}/../../Containerfile"
    dest: "/opt/{{ project_name }}/Containerfile"
- name: Build container image
  ansible.builtin.command:
    cmd: podman build -t {{ project_name }}:latest -f Containerfile .
    chdir: "/opt/{{ project_name }}"
- name: Run container
  ansible.builtin.command:
    cmd: podman run -d --name {{ project_name }} -p {{ app_port }}:{{ app_port }} {{ project_name }}:latest

roles/app/templates/app.env.j2:

APP_PORT={{ app_port }}

Add environment variables for detected dependencies.

group_vars/all.yaml:

project_name: {project_name}
app_port: {port}

Add variables for detected dependencies (db_host, redis_host, etc.).

Phase 5 — Summary

After generation, print a summary:

Generated infrastructure files:

  Containerfile + podman-compose + scripts (if selected)
    - Containerfile
    - podman-compose.yaml
    - start.sh / stop.sh / test.sh

  Kubernetes manifests (if selected)
    - infra/k8s/namespace.yaml
    - infra/k8s/deployment.yaml
    - infra/k8s/service.yaml
    - infra/k8s/configmap.yaml
    - infra/k8s/hpa.yaml

  Helm chart (if selected)
    - infra/helm/Chart.yaml
    - infra/helm/values.yaml
    - infra/helm/templates/*.yaml

  OpenTofu modules (if selected)
    - infra/tofu/providers.tf
    - infra/tofu/backend.tf
    - infra/tofu/variables.tf
    - infra/tofu/outputs.tf
    - infra/tofu/{selected_patterns}.tf

  Ansible playbooks (if selected)
    - infra/ansible/playbook.yaml
    - infra/ansible/inventory/hosts.yaml
    - infra/ansible/roles/app/tasks/main.yaml
    - infra/ansible/group_vars/all.yaml

Next steps:
  - Review generated files and adjust values for your environment
  - For Containerfile: run ./start.sh to build and start
  - For K8s: kubectl apply -f infra/k8s/
  - For Helm: helm install {project_name} infra/helm/
  - For OpenTofu: cd infra/tofu && tofu init && tofu plan
  - For Ansible: cd infra/ansible && ansible-playbook -i inventory/hosts.yaml playbook.yaml

Rules

  • Never write comments in generated files
  • Never use docker, always use podman and podman-compose
  • Keep generated files compact, no unnecessary blank lines
  • Sleep values in scripts never exceed 1 second
  • Use loops with condition checks for readiness waits
  • All generated files must be valid and ready to use with minimal edits
  • Warn before overwriting existing files
  • Use the detected project name, port, and dependencies consistently across all generated files