Infrastructure as Code: Terraform & Ansible Services

Infrastructure as code replaces console clicks and one-off shell commands with files your team can review, version and apply the same way every time. Servers, firewalls, DNS records, databases and cloud accounts become a description in Git: a new staging environment is the production description with different variables, a rebuild after a failure is a command, and every change shows up as a plan that lists exactly what will be created, modified or destroyed.

At CodeWiz we write Terraform and OpenTofu modules, CloudFormation templates where Amazon Web Services is the only cloud, and Ansible playbooks that configure the servers those tools create. We set up remote state, secrets, code review and a pipeline so a change to infrastructure goes through the same discipline as a change to application code. The handover is a repository your team can run, not a diagram that goes stale the week after launch.

This service is for teams whose staging never quite matches production, agencies that rebuild similar stacks for every client, and companies that have grown a handful of servers nobody wants to touch because the setup lives in one person’s memory. If creating a new environment currently takes a day of manual steps, describing it in code is usually the change that makes the next one take an hour of review.

Code: Server racks with neatly routed blue and white network cables

Infrastructure as Code with Terraform and OpenTofu

Terraform configuration describes cloud resources in HashiCorp Configuration Language and talks to providers for AWS, Hetzner, DigitalOcean, Cloudflare and dozens of other platforms. OpenTofu is the open-source fork that reads the same language and the same providers, which is the default we recommend when there is no reason to stay on a commercial Terraform edition. You declare what should exist; the tool compares that declaration with what is actually there and applies only the difference.

How we structure a repository

  • Small modules with a clear input and output: a server, a managed database, a firewall, a DNS zone. Environments compose those modules instead of copying them.
  • One state file per environment and per major component, so a typo in a DNS change cannot plan the destruction of a database.
  • Pinned provider and module versions, formatted with terraform fmt and checked with tflint and a configuration scanner such as Trivy before anyone applies.
  • A pipeline that runs terraform plan on every pull request and posts the plan as a comment. Apply happens from the pipeline on a protected branch, not from a laptop.

A small module call is enough to show the shape. The environment name, the firewall and the DNS record all come from variables, so staging and production share the module and differ only in values:

variable "environment" { type = string } module "app" { source = "./modules/server" name = "app-${var.environment}" image = "ubuntu-24.04" firewall = ["http", "https", "ssh-admin"] backups = var.environment == "production" } resource "cloudflare_record" "app" { zone_id = var.zone_id name = var.hostname type = "A" content = module.app.ipv4 proxied = true }

We use this for VPS fleets on Hetzner and DigitalOcean, for AWS accounts, and for Cloudflare DNS, cache and firewall rules that would otherwise drift after every dashboard edit. HashiCorp’s Terraform language documentation is the syntax reference. Where the servers actually run is a separate decision, covered in our guide to deployment across DigitalOcean, AWS, Hetzner and bare metal.

CloudFormation Templates for AWS-Only Estates

CloudFormation is AWS’s native way to describe a stack of resources in YAML or JSON. AWS stores the state, detects drift and can roll a failed update back. Templates are a reasonable source of truth when the company is all-in on AWS and already thinks in accounts, IAM and StackSets. For everyone else, a multi-cloud tool is the smaller long-term commitment.

What we deliver when CloudFormation is the right fit

  • Nested stacks with a clear boundary: network, data, application, edge. A change set is reviewed before it runs, which is the AWS equivalent of reading a Terraform plan.
  • Parameters for anything that differs between accounts, and secrets read from Systems Manager or Secrets Manager rather than written into the template.
  • DeletionPolicy: Retain and termination protection on databases, buckets and anything else that must survive a stack delete.
  • StackSets when the same baseline (logging, guardrails, IAM) has to land in several accounts.
  • The AWS CDK when a team would rather generate those templates from TypeScript or Python, with the synthesized template still committed so review stays possible.

We do not rewrite a working Terraform estate into CloudFormation for its own sake. The AWS CloudFormation user guide covers the service itself; our job is to decide whether you need it and, if you do, to leave you with stacks someone other than the original author can change.

Ansible Automation for Configuration and Releases

Terraform and CloudFormation are good at creating infrastructure and a poor fit for what happens inside a server: packages, users, SSH policy, PHP or Node.js versions, Nginx, cron and the steps of a release. Ansible automation covers that layer. Playbooks are YAML, they are safe to run again, and they work over SSH against machines that Terraform never created.

A playbook we would actually leave with you

Roles stay small and versioned. Inventories and group_vars hold the differences between environments. Secrets sit in Ansible Vault or, better, in an external secret store the playbook reads at runtime. A baseline role hardens SSH, installs unattended security upgrades and sets up log shipping; application roles install the runtime and deploy a specific release.

- name: Baseline application server hosts: app become: true roles: - role: baseline - role: nginx - role: php vars: php_version: "8.3" tasks: - name: Enable unattended security upgrades ansible.builtin.apt: name: unattended-upgrades state: present
  • ansible-lint and a check-mode run in CI, so a pull request shows what would change before anything touches a server.
  • Serial batches and handlers, so a rolling change to Nginx does not restart every machine at once.
  • Tags that let you run a single role, and a documented command for a full converge.
  • No passwords in the repository. Vault passwords and SSH keys live with the same care as production database credentials.

The official Ansible documentation is the reference for modules and inventories. We draw a hard line between the two tools: Terraform creates the machine and its firewall, Ansible configures what runs on it. When both try to own the same resource, drift is guaranteed.

Network patch panel with many tangled cables plugged into numbered ports

Environment Management for Dev, Staging and Production

Environment management is what makes the rest trustworthy. A module that only production uses, with staging built by hand, will lie to you the day a release depends on the difference. We keep one description and let variables, size and access differ.

EnvironmentWhere it runsDataWho may apply changes
DevelopmentA laptop or a small shared serverFixtures or a tiny synthetic setAny developer, often applied automatically
StagingThe same shape as production, smallerAnonymized or syntheticThe pipeline, on merge to the main branch
ProductionReal capacity and real backupsCustomer dataThe pipeline, from a protected branch, after a human reads the plan
  • Separate state and separate credentials per environment. A staging apply cannot see production.
  • Naming and tags that include the environment, the owner and the repository, so a forgotten server is obvious in the cloud bill.
  • Staging that is allowed to be smaller, but not a different architecture. If production is two app servers behind a load balancer, staging is too, even if each one is cheaper.
  • A documented way to refresh staging data from production without copying secrets, personal data you have no basis to copy, or payment details.

Terraform, OpenTofu, CloudFormation or Ansible

Teams often ask us to pick a single tool. The useful answer is a pair with a clear boundary, chosen from where the infrastructure actually lives.

ApproachWhat it manages wellStateBest fitTrade-off
Terraform or OpenTofuServers, networks, DNS, databases, object storage, CloudflareRemote state you operateMore than one cloud, or a cloud plus CloudflareState locking, provider upgrades, a discipline around apply
CloudFormationAWS resources, account baselines, StackSetsStored and locked by AWSAn estate that is and will stay on AWSNo help the day you add Hetzner, DigitalOcean or another cloud
AnsiblePackages, users, web servers, app releasesMostly statelessConfiguring machines that already existAwkward as the only tool for creating cloud resources
Terraform plus AnsibleCreate with one, configure with the otherSplit on purposeThe usual production setup we leave behindTwo tools to learn, and a written rule for which one owns what

Cloudflare settings belong in the same repository as the servers they point at. A proxy, a cache rule or a firewall change made only in the dashboard will not be there when you rebuild, which is why we treat that configuration as code too. We have written separately about Cloudflare configuration for performance.

State, Drift, Secrets and Review

The files are the easy part. The habits around them are what stop a confident plan from breaking production.

  • Remote state with locking. State lives in an encrypted backend (an S3 bucket with a lock, a GitLab HTTP backend, or an equivalent), never in Git and never only on a laptop. State often contains secrets and resource IDs; access to it is production access.
  • Plans are reviewed. Pull requests show the plan. Destructive changes are called out. Production apply uses a role that humans do not use for everyday cloud console work.
  • Drift is detected on a schedule. A daily plan that reports “something changed outside the code” is how you find the console click before it becomes an incident. Ansible’s check mode does the same job inside the server.
  • Existing infrastructure can be adopted. terraform import and import blocks bring a live server under management without recreating it. We do this brownfield work more often than greenfield.
  • Secrets stay out of the repository. Variable files that hold passwords are not committed. Ansible Vault, SOPS or a cloud secret manager holds them, and CI receives only the access it needs for that environment.

Common Infrastructure as Code Mistakes We Fix

  • One state file for the entire company, so every plan is slow and every apply is frightening.
  • Applying from a developer laptop with a personal cloud key, and no record of who changed production.
  • Unpinned providers, so a routine init pulls a major version and proposes replacements.
  • Terraform and a panel, or Terraform and Ansible, both managing the same firewall rule.
  • Staging that was built by hand and has quietly diverged in PHP version, cron or environment variables.
  • Secrets in terraform.tfvars committed next to the code.
  • No backend locking, so two applies run at once and corrupt the state.

Not every site needs this. A single WordPress install on a well-run server, managed through an open-source hosting panel, can be the simpler and cheaper setup. If that is your situation, we will say so.

What Drives the Cost of an Infrastructure as Code Project

We estimate after a short inventory of accounts, servers and the way you deploy today. The factors that move the number are consistent:

  • How many clouds, accounts and environments exist, and how different they really are.
  • Whether we are describing something new or importing years of click-built resources.
  • How much of the setup is undocumented, including firewall exceptions and cron jobs someone added over SSH.
  • Whether Ansible configuration, CI integration and secret management are in scope or only the cloud resources.
  • Policy and audit requirements: who may apply, how long plans are kept, and whether configuration scanning is required.
  • A handover with documentation versus an ongoing arrangement where we keep modules and providers current.

How We Work

  1. Inventory: we list accounts, servers, DNS, databases, firewall rules and the steps someone currently performs by hand.
  2. Design: we choose Terraform, OpenTofu, CloudFormation and Ansible, draw the boundary between them, and agree which environments are in scope.
  3. Describe: we write modules, templates and playbooks, and import existing resources so production is not rebuilt just to bring it under management.
  4. Review: plans and check-mode runs land on pull requests. We fix destructive surprises before anything is applied.
  5. Apply: staging first, then production from the pipeline, with a rollback path for the application and a documented way to reverse an infrastructure change.
  6. Handover: you get the repository, the runbook and a walkthrough. We can keep providers, roles and drift checks current afterwards.

Why CodeWiz

  • We run what we describe: Laravel, Node.js, WordPress and the servers under them are work we already do, so the modules match how the applications actually behave.
  • Open-source first: OpenTofu, Ansible, tflint and Trivy keep you off a licence you do not need.
  • Sized to the problem: a single server does not get a multi-account platform. A fleet that has outgrown click-ops does not get a README of SSH commands.
  • Fair pricing: premium development without premium prices, from Banja Luka, working remotely with clients locally and internationally.
  • One team for the whole stack: infrastructure, deployments, performance and security sit together. More about how we work is on the about CodeWiz page.

Frequently Asked Questions

How much does infrastructure as code cost?

It depends on how many accounts and servers you have, and on whether we are describing a new setup or importing one that was built by hand. A single environment with a few servers is a short engagement. Several clouds, years of undocumented changes and a full Ansible baseline take longer. You get a written estimate after the inventory, before we start.

Should we use Terraform or Ansible?

Usually both, for different jobs. Terraform or OpenTofu creates cloud resources. Ansible configures the operating system and deploys the application. CloudFormation replaces Terraform only when the estate is AWS and is going to stay there.

Do we have to rebuild production to adopt this?

No. Importing an existing server, database or DNS record brings it under management without recreating it. We plan that import, read the plan carefully, and apply it only when the plan shows no replacement.

How long does a typical project take?

A small, well-understood stack is often described, imported and wired into CI within a couple of weeks. A brownfield estate with several environments and little documentation takes longer, because the inventory is part of the work.

Who runs the tooling after handover?

Your team can. The pipeline applies changes, and the runbook covers plan, apply, rollback and adding a server. We can keep the modules maintained if you want that.

Will this lock us into one cloud?

The opposite, when we use OpenTofu or Terraform. Providers for Hetzner, DigitalOcean, AWS and Cloudflare live side by side. What you commit to is the discipline of changing infrastructure through review, which travels with you if a provider changes.

Ready to Put Your Infrastructure in Code?

Tell us which clouds and servers you have, and where a change still depends on someone clicking or SSH-ing. Contact us and we will propose a scope, or see the rest of our infrastructure as code services.