AI

Using AI Coding Agents for DevOps: Terraform, Ansible, and Kubernetes with OpenCode

AI coding agents are not just for web developers cranking out React components. If you spend your days writing Terraform modules, Ansible playbooks, Kubernetes manifests, and bash scripts, a terminal agent fits straight into that workflow. opencode is one of the strongest options: an open-source agent that lives in your terminal, reads your project, and generates, reviews, and refactors code against whatever LLM provider you point it at.

Original content from computingforgeeks.com - post 165425

This guide walks through practical examples of using opencode to produce DevOps infrastructure code, with honest assessments of what it gets right and where you still apply your own judgment. We cover Terraform, Ansible, Kubernetes YAML, and shell scripting. The Terraform example was generated with opencode 1.18.4 on Ubuntu 26.04 in July 2026 against Anthropic’s Claude and checked with terraform validate on a real machine; the Ansible, Kubernetes, and shell examples are representative of the output opencode produces.

What you’ll learn

  • Installing opencode and pointing it at an LLM provider with an API key
  • Generating a real Terraform module with a single prompt and validating it
  • Producing Ansible playbooks with SELinux, firewalld, and handler considerations
  • Creating Kubernetes Deployment, Service, and Ingress manifests from a description
  • Writing shell scripts with proper error handling and logging
  • Using plan mode and subagents to keep larger tasks under control
  • What AI agents consistently get wrong with infrastructure code, and how to catch it

A quick note on which opencode this is

The name has some history. The original opencode was a Go project; after a governance dispute in 2025 that fork became Charm’s “Crush”, while the team behind SST kept the opencode name and rewrote it in TypeScript. That TypeScript rewrite, now maintained by Anomaly at github.com/anomalyco/opencode (MIT licensed), is the tool this guide covers. If you followed an older link to sst/opencode, it now redirects there.

Install opencode and point it at a provider

The official installer drops a single binary on your PATH. It needs no Node.js runtime:

curl -fsSL https://opencode.ai/install | bash

If you prefer a package manager, the npm package is opencode-ai (not opencode), and Homebrew, Scoop, and the AUR are supported too:

npm install -g opencode-ai

Confirm the version before going further:

opencode --version

opencode talks to 75+ providers through the Models.dev catalog. The quickest interactive path is opencode auth login, which stores credentials under ~/.local/share/opencode/auth.json. For a scripted setup, keep the API key in an environment variable and reference it from the config file so the key never lives in the repo. Create the config:

vim ~/.config/opencode/opencode.json

Point it at Anthropic’s Claude and pull the key from the environment:

{
  "$schema": "https://opencode.ai/config.json",
  "model": "anthropic/claude-sonnet-4-5",
  "provider": {
    "anthropic": { "options": { "apiKey": "{env:ANTHROPIC_API_KEY}" } }
  }
}

Export the key in your shell (or drop it in a project .env). opencode substitutes it at runtime:

export ANTHROPIC_API_KEY="your-api-key-here"

How AI agents handle infrastructure code

AI coding agents work best when they have clear constraints: a specific cloud provider, a target OS, a defined architecture. Vague prompts like “set up my infrastructure” produce vague results. Specific prompts like “create a Terraform module for an AWS VPC with two public subnets, two private subnets, and a NAT gateway in us-east-1” produce code you can actually use.

What works well: generating boilerplate (variable definitions, output blocks, resource scaffolding), following established patterns (three-tier architectures, standard Kubernetes deployment specs), and producing syntactically correct HCL, YAML, and bash. Where it falls short: understanding your specific environment’s constraints, getting provider version pinning right, and handling complex state dependencies. Treat AI-generated infrastructure code the same way you would treat a pull request from a junior engineer. Review everything, run terraform plan, test in staging.

Generating Terraform code with opencode

opencode has two modes. Run it with no arguments to open the interactive TUI, or use opencode run for a one-shot, scriptable prompt that is ideal for CI. Working in the target project directory lets the agent see your existing files. Generate a VPC module in one command:

cd ~/tf-vpc
opencode run --model anthropic/claude-sonnet-4-5 "Create a Terraform module for an AWS VPC with 2 public subnets, 2 private subnets, and a NAT gateway. Split it into main.tf, variables.tf, and outputs.tf, targeting AWS provider 5.x."

The agent reports each file as it writes it, then summarizes what it built:

opencode 1.18.4 generating a Terraform VPC module with the anthropic Claude provider

The generated variables.tf exposes clean, typed inputs with sensible defaults:

variable "aws_region" {
  description = "AWS region to deploy resources"
  type        = string
  default     = "us-east-1"
}

variable "vpc_cidr" {
  description = "CIDR block for the VPC"
  type        = string
  default     = "10.0.0.0/16"
}

variable "public_subnet_cidrs" {
  description = "CIDR blocks for public subnets"
  type        = list(string)
  default     = ["10.0.1.0/24", "10.0.2.0/24"]
}

variable "private_subnet_cidrs" {
  description = "CIDR blocks for private subnets"
  type        = list(string)
  default     = ["10.0.10.0/24", "10.0.11.0/24"]
}

The main.tf wires up the VPC, an internet gateway, the four subnets, a NAT gateway, and route tables. One detail stands out: rather than hardcoding availability zones, the agent added a data source to look them up at plan time.

resource "aws_subnet" "public" {
  count                   = 2
  vpc_id                  = aws_vpc.main.id
  cidr_block              = var.public_subnet_cidrs[count.index]
  availability_zone       = data.aws_availability_zones.available.names[count.index]
  map_public_ip_on_launch = true

  tags = {
    Name = "${var.project_name}-public-subnet-${count.index + 1}"
    Type = "Public"
  }
}

resource "aws_nat_gateway" "main" {
  allocation_id = aws_eip.nat.id
  subnet_id     = aws_subnet.public[0].id

  tags = {
    Name = "${var.project_name}-nat-gateway"
  }

  depends_on = [aws_internet_gateway.main]
}

data "aws_availability_zones" "available" {
  state = "available"
}

Reviewing and validating the generated Terraform

Generated code is a first draft, so run it through the same tooling you would use on a colleague’s pull request. Formatting first, then a full validate:

terraform fmt -recursive
terraform init
terraform validate

The output tells the real story. terraform fmt reported that main.tf needed reformatting (the agent left a couple of stray blank-line spaces), but after formatting, init and validate both passed cleanly:

Validating the opencode-generated Terraform module with terraform fmt init and validate

Valid HCL is not the same as production-ready HCL. Before terraform apply, check the things a validator cannot:

  • Provider version: the ~> 5.0 constraint is broad. Pin it tighter in production based on what you actually run. Our Terraform install guide covers version management.
  • CIDR ranges: make sure they do not overlap with existing VPCs or on-premises networks.
  • Single NAT gateway: fine for dev, but production workloads want one per availability zone for high availability.
  • No VPC flow logs: add those for compliance and debugging.

Iterating on the module

The real payoff is iteration. Because opencode reads the files already in the directory, a follow-up prompt extends the module in context:

opencode run "Add a bastion host in the first public subnet with a security group allowing SSH from a configurable CIDR. Use the Amazon Linux 2023 AMI and a variable for the SSH key name."

It reads the existing subnets and VPC, then adds the bastion resources that reference them. That context awareness is what separates an agent from a snippet generator. Validate every iteration the same way, and read the terraform plan before you commit to anything.

Generating Ansible playbooks

Ansible generation is a strong fit because playbooks follow predictable patterns: install packages, template configs, manage services, set firewall rules. Give opencode a clear target:

opencode run "Write an Ansible playbook that installs Nginx on Rocky Linux 9, configures a reverse proxy to a backend on port 8080, opens firewall ports 80 and 443, sets up Let's Encrypt SSL with certbot, and handles SELinux booleans. Use handlers for service restarts."

The kind of playbook it produces uses fully qualified collection names and correct RHEL-family idioms:

---
- name: Configure Nginx reverse proxy with SSL
  hosts: webservers
  become: true
  vars:
    backend_port: 8080
    domain_name: app.example.com
    certbot_email: [email protected]

  tasks:
    - name: Install Nginx and certbot
      ansible.builtin.dnf:
        name:
          - nginx
          - certbot
          - python3-certbot-nginx
        state: present

    - name: Allow SELinux httpd network connections
      ansible.posix.seboolean:
        name: httpd_can_network_connect
        state: true
        persistent: true

    - name: Open HTTP and HTTPS in firewalld
      ansible.posix.firewalld:
        service: "{{ item }}"
        permanent: true
        immediate: true
        state: enabled
      loop:
        - http
        - https

    - name: Deploy Nginx reverse proxy config
      ansible.builtin.template:
        src: templates/nginx-reverse-proxy.conf.j2
        dest: /etc/nginx/conf.d/{{ domain_name }}.conf
        owner: root
        group: root
        mode: "0644"
      notify: Reload Nginx

  handlers:
    - name: Reload Nginx
      ansible.builtin.systemd:
        name: nginx
        state: reloaded

The httpd_can_network_connect SELinux boolean is essential for Nginx to proxy to a backend port on RHEL-family systems, and the handler reloads Nginx only when the config actually changes. See our Ansible playbook tutorial for the fundamentals behind this structure.

What to fix before running

  • The playbook assumes the domain already resolves to the server. Add a verification task or document it as a prerequisite.
  • The Jinja2 templates it references (nginx-reverse-proxy.conf.j2) are not generated. Ask for those in a follow-up prompt.
  • Add an nginx -t validation step before reloading to catch syntax errors in the generated config.

A second pass catches most of this. Ask opencode to review its own output with a fresh prompt:

opencode run "Review this Ansible playbook for production readiness. Check idempotency, error handling, and security, and list concrete fixes."

Running a review pass in a separate turn works well because the agent evaluates the finished artifact instead of defending the code it just wrote.

Kubernetes manifests

Kubernetes YAML is verbose and pattern-heavy, which makes it an ideal candidate for generation. Most deployments are a Deployment, a Service, and maybe an Ingress or HPA.

opencode run "Create Kubernetes manifests for a Python Flask app: a Deployment with 3 replicas, resource limits, health checks, and a non-root security context. Add a ClusterIP Service and an Ingress with TLS. Use the image registry.example.com/flask-app:1.2.0."

The Deployment it generates includes a non-root security context and probes, which many hand-written manifests skip:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: flask-app
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    spec:
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
        fsGroup: 1000
      containers:
        - name: flask-app
          image: registry.example.com/flask-app:1.2.0
          ports:
            - containerPort: 5000
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              cpu: 500m
              memory: 256Mi
          livenessProbe:
            httpGet:
              path: /healthz
              port: 5000
            initialDelaySeconds: 10
            periodSeconds: 15
          readinessProbe:
            httpGet:
              path: /ready
              port: 5000
            initialDelaySeconds: 5
            periodSeconds: 10

The maxUnavailable: 0 rolling update ensures zero downtime during deploys. Before applying anything, preview the diff against your live cluster:

kubectl diff -f flask-app/

A few things to check: the /healthz and /ready endpoints must actually exist in your app or the pods will crashloop, the Ingress assumes cert-manager plus an nginx ingress controller, and no PodDisruptionBudget was generated. If you are standing up a cluster to test against, our guide to installing a Kubernetes cluster on Ubuntu gets you there.

Shell scripts and automation

Backup scripts, deployment wrappers, log rotation, health checks. Agents tend to produce scripts with better structure and error handling than the ones that grew organically over years. A prompt with explicit requirements pays off:

opencode run "Write a bash script that dumps all PostgreSQL databases, compresses each with zstd, uploads to an S3 bucket with a date prefix, removes local dumps older than 7 days, and logs to syslog. Include error handling and exit on failure."

The result opens with strict mode and a cleanup trap, which is exactly what you want in a cron job:

#!/usr/bin/env bash
set -euo pipefail

S3_BUCKET="s3://company-backups/postgresql"
BACKUP_DIR="/var/backups/postgresql"
RETENTION_DAYS=7
DATE_PREFIX=$(date +%Y-%m-%d)
LOG_TAG="pg-backup"

log() {
    logger -t "$LOG_TAG" "$1"
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}

cleanup() {
    local exit_code=$?
    [[ $exit_code -ne 0 ]] && log "ERROR: Backup failed with exit code $exit_code"
    exit $exit_code
}

trap cleanup EXIT
mkdir -p "$BACKUP_DIR"
log "Starting PostgreSQL backup"

find "$BACKUP_DIR" -name "*.sql.zst" -mtime +$RETENTION_DAYS -delete

The set -euo pipefail catches errors early and the trap logs failures even on a non-zero exit. Before trusting any generated script, run it through shellcheck, which catches quoting bugs and unset-variable footguns the agent misses. For dumps over a few gigabytes, switch pg_dump to --format=custom with --jobs for parallel dumping.

Plan mode and subagents for bigger tasks

Single-file prompts are fine for a module or a playbook. For anything spanning several files, such as a pipeline with a Dockerfile, a workflow, and Helm values, lean on two built-in features instead of firing off one giant prompt.

Plan mode. In the interactive TUI, press Tab to toggle between Build and Plan mode. Plan mode disables file edits and has the agent propose an approach first, so you approve the shape of the work before a single file is written. It is the difference between “here is what I would do” and “here are 12 files I already changed.”

Subagents. opencode can delegate parts of a task to focused subagents, and it generates an AGENTS.md for your project when you run /init, giving the agent persistent rules about your stack. Kick off a multi-file task and let it coordinate the pieces:

opencode run "Set up a CI/CD pipeline: a GitHub Actions workflow that builds a Docker image, runs a Trivy scan, and deploys to EKS staging with Helm. Include a multi-stage Dockerfile with a non-root user and separate values files for staging and production."

Because one agent plans the whole thing before delegating, the pieces reference each other correctly: the workflow points at the right Helm values files and the image tag propagates through every step. If you need heavier orchestration than opencode’s built-in agents provide, Oh My OpenAgent is a separate third-party harness that layers a larger multi-agent workflow on top of opencode. It is an independent project, not part of opencode, and not required for anything in this guide.

Best practices for AI-generated infrastructure code

Always dry-run before applying. Every tool in the DevOps ecosystem has a preview mode. Use it.

terraform plan -out=tfplan
ansible-playbook site.yml --check --diff
kubectl diff -f manifests/
shellcheck backup-script.sh

Pin versions explicitly. Agents tend toward loose constraints or none at all. Lock down provider versions in Terraform, collection versions in Ansible, and image tags in Kubernetes. A latest tag in a Deployment is a ticking time bomb.

Review IAM and RBAC carefully. Agents err toward permissiveness because tight permissions break things during generation. An Action: "*" in a generated IAM policy is functional but violates least privilege. Narrow it to the specific actions the workload needs.

Test in isolation first. Deploy generated code to a throwaway environment (a dedicated Terraform workspace, a Kind cluster) before it touches staging. And run a separate review pass: asking opencode to critique the finished artifact in a fresh turn catches issues the first pass missed.

What AI agents get wrong

Honesty about limitations matters more than hype. After extended use for infrastructure code, these are the patterns that consistently need correction.

Outdated provider and module versions. Models have training cutoffs, so an agent might reach for AWS provider 4.x syntax where 5.x changed the API. Check the provider changelog and run terraform init -upgrade to catch incompatibilities.

SELinux and AppArmor as an afterthought. Most generated playbooks assume permissive mode. On RHEL-family systems with SELinux enforcing, a missing setsebool or semanage causes silent failures that are painful to debug. Check ausearch -m avc -ts recent after deploying generated configs.

Generic firewall and security-group rules. Agents open wider ranges than necessary. A generated security group allowing 0.0.0.0/0 on port 22 is technically correct and terrible practice. Restrict source CIDRs to your bastion or VPN ranges.

Secrets in plain text. Generated code sometimes drops passwords or API keys straight into YAML or shell. Always move secrets to Vault, AWS Secrets Manager, Kubernetes Secrets (or the External Secrets Operator), or an encrypted Ansible Vault file.

Frequently asked questions

Can AI agents replace DevOps engineers?

No. Agents accelerate the repetitive, pattern-based parts of DevOps: writing boilerplate, scaffolding standard architectures, generating initial manifests. The judgment calls, which architecture to use, how to handle failure modes, where the security boundary sits, still need a human who understands the production environment. Treat an agent as a faster way to get a first draft you then refine.

Which LLM model works best for infrastructure code?

In our testing, Anthropic’s Claude (Sonnet) produced the most accurate Terraform and Kubernetes code, which is why the examples here use anthropic/claude-sonnet-4-5. Because opencode is provider-agnostic, you can switch models with /models in the TUI or the -m flag on opencode run and compare for yourself. The model matters less than prompt specificity: a detailed prompt with constraints beats a vague prompt on any model.

Is AI-generated infrastructure code safe for production?

With review, yes. Generated code needs the same scrutiny as any pull request: check for overly permissive IAM, validate resource limits, verify version pins, and test in staging. The tooling to catch problems already exists (terraform plan, ansible --check, kubectl diff, checkov, tfsec, shellcheck). Use it. Skip the review step and you will learn why the hard way. If you want to compare opencode against other agents first, see our Claude Code cheat sheet for a different take on the same workflow.

Keep reading

Claude Code Cheat Sheet – Commands, Shortcuts, Tips AI Claude Code Cheat Sheet – Commands, Shortcuts, Tips Ollama Models Cheat Sheet 2026 (gpt-oss, Qwen3-Coder, DeepSeek) AI Ollama Models Cheat Sheet 2026 (gpt-oss, Qwen3-Coder, DeepSeek) OpenCode CLI Cheat Sheet – Commands and Workflows AI OpenCode CLI Cheat Sheet – Commands and Workflows Best Machine Learning and Statistical Learning Books for 2026 AI Best Machine Learning and Statistical Learning Books for 2026 Claude Fable 5.1 Released: Benchmarks, Pricing, and API Changes AI Claude Fable 5.1 Released: Benchmarks, Pricing, and API Changes Deploy and Use OpenEBS Container Storage on Kubernetes Containers Deploy and Use OpenEBS Container Storage on Kubernetes

Leave a Comment

Press ESC to close