Client Area

Infrastructure as Code on DomainIndia VPS: Terraform and Ansible

ByDomain India Team·DomainIndia Engineering
5 min readPublished 23 Apr 2026Updated 23 Jun 2026141 views

In this article

  • 1Why IaC
  • 2Terraform vs Ansible
  • 3Part 1 — Terraform for DomainIndia
  • 4Part 2 — Ansible for server configuration
  • 5Part 3 — Ansible Vault for secrets

Infrastructure as Code on DomainIndia VPS: Terraform and Ansible

TL;DR
Replace "SSH in and run commands" with reproducible, version-controlled infrastructure. Terraform provisions servers and resources; Ansible configures them. This guide shows a working stack for a DomainIndia VPS fleet — from zero to running services with a single terraform apply + ansible-playbook.

Why IaC

If you have 1 server, manual is fine. If you have 3+ (staging, production, disaster-recovery) or rebuild servers often, IaC is non-negotiable. Benefits:

  • Every change is a git commit (auditable)
  • Rebuild a lost server in 30 minutes, not 3 days
  • Same config across dev/staging/production — "works on staging" actually means something
  • Team-ready — two engineers don't step on each other

Terraform vs Ansible

ToolRoleDeclarativeBest for
TerraformProvisioning (create VMs, DNS, load balancers)Yes — desired state"What infrastructure exists"
AnsibleConfiguration (install software, copy files, run scripts)Partial — playbooks are imperative"What's installed and configured"

Used together: Terraform creates the VPS, Ansible configures it.

Part 1 — Terraform for DomainIndia

DomainIndia VPS isn't a native Terraform provider, but you can manage DNS, Cloudflare, AWS S3 (backups), and remote VPS state via standard providers. For pure VM provisioning on DomainIndia you can use the null_resource + remote-exec pattern, or Order the VPS manually and manage IPs in Terraform as inputs.

Example main.tf — Cloudflare DNS + AWS S3 backup bucket:

hcl
terraform {
  required_providers {
    cloudflare = { source = "cloudflare/cloudflare", version = "~> 4.0" }
    aws        = { source = "hashicorp/aws", version = "~> 5.0" }
  }
}

provider "cloudflare" { api_token = var.cloudflare_token }
provider "aws"        { region = "ap-south-1" }

resource "cloudflare_record" "api" {
  zone_id = var.cloudflare_zone_id
  name    = "api"
  value   = var.api_vps_ip    # DomainIndia VPS IP
  type    = "A"
  proxied = true
  ttl     = 1
}

resource "aws_s3_bucket" "backups" {
  bucket = "yourcompany-vps-backups"
  tags   = { Environment = "production" }
}

resource "aws_s3_bucket_lifecycle_configuration" "backups" {
  bucket = aws_s3_bucket.backups.id
  rule {
    id     = "expire-old"
    status = "Enabled"
    expiration { days = 90 }
  }
}

variables.tf:

hcl
variable "cloudflare_token"   { type = string; sensitive = true }
variable "cloudflare_zone_id" { type = string }
variable "api_vps_ip"         { type = string }

terraform.tfvars (git-ignored):

cloudflare_token   = "your-token"
cloudflare_zone_id = "abc123"
api_vps_ip         = "65.109.X.X"

Apply:

bash
terraform init
terraform plan
terraform apply

Part 2 — Ansible for server configuration

Ansible reads a YAML "playbook" and runs steps over SSH. No agent, no daemon — just SSH + Python.

Install Ansible on your laptop:

bash
pip install ansible

Create inventory.ini:

ini
[web]
api.yourcompany.com

[web:vars]
ansible_user=deploy
ansible_ssh_private_key_file=~/.ssh/id_rsa

Create site.yml:

yaml
---
- name: Configure API server
  hosts: web
  become: yes
  vars:
    app_user: goapp
    app_dir: /home/goapp/app
  tasks:
    - name: Install essentials
      package:
        name:
          - nginx
          - certbot
          - python3-certbot-nginx
          - postgresql
          - fail2ban
        state: present

    - name: Create app user
      user:
        name: "{{ app_user }}"
        shell: /bin/bash
        home: "/home/{{ app_user }}"
        create_home: yes

    - name: Deploy nginx config
      template:
        src: templates/nginx.conf.j2
        dest: /etc/nginx/conf.d/api.conf
      notify: reload nginx

    - name: Start services
      systemd:
        name: "{{ item }}"
        enabled: yes
        state: started
      loop: [nginx, postgresql, fail2ban]

    - name: Allow HTTP/HTTPS through firewall
      firewalld:
        service: "{{ item }}"
        permanent: yes
        immediate: yes
        state: enabled
      loop: [http, https]

  handlers:
    - name: reload nginx
      systemd: { name: nginx, state: reloaded }

Run:

bash
ansible-playbook -i inventory.ini site.yml

Every step is idempotent — run it 100 times, the end state is the same.

Part 3 — Ansible Vault for secrets

Never commit passwords in plaintext. Ansible Vault encrypts files:

bash
ansible-vault create group_vars/all/secrets.yml
# opens editor, type:
db_password: "s3cr3t!"
api_token: "sk-..."

Reference in playbooks normally: "{{ db_password }}". Run with --ask-vault-pass.

Store the vault password in a file outside git:

bash
ansible-playbook -i inventory.ini site.yml --vault-password-file ~/.vault_pass

Part 4 — Common patterns

Pattern 1: Rolling deployment

yaml
- hosts: web
  serial: 1   # one server at a time
  tasks:
    - name: Drain from load balancer
      uri: { url: "http://lb/drain/{{ inventory_hostname }}", method: POST }
    - name: Deploy new binary
      copy: { src: ./myapp, dest: "{{ app_dir }}/myapp", mode: 0755 }
    - name: Restart service
      systemd: { name: myapp, state: restarted }
    - name: Wait for health
      uri: { url: "http://localhost:8080/health", status_code: 200 }
      retries: 10
      delay: 3
    - name: Add back to load balancer
      uri: { url: "http://lb/activate/{{ inventory_hostname }}", method: POST }

Pattern 2: Disaster recovery dry-run

bash
# Destroy staging and rebuild from scratch
terraform destroy -var-file=staging.tfvars
terraform apply -var-file=staging.tfvars
ansible-playbook -i inventory-staging.ini site.yml

GitOps: CI/CD for IaC

Push changes → GitHub Actions → terraform plan → PR comment with diff → human review → merge → auto-apply.

Example .github/workflows/terraform.yml:

yaml
on: [pull_request, push]
jobs:
  terraform:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3
      - run: terraform init
      - run: terraform plan -no-color
        if: github.event_name == 'pull_request'
      - run: terraform apply -auto-approve
        if: github.ref == 'refs/heads/main'

Secrets (TF_VAR_cloudflare_token etc.) go in Repo Settings → Secrets.

Common pitfalls

FAQ

Q Do I need IaC for one VPS?

Not strictly. But writing Ansible playbooks for even a single server is good discipline — you document the setup, can rebuild quickly, and can grow to a fleet later.

Q Terraform vs Pulumi vs OpenTofu?

OpenTofu is a fork of Terraform (licence dispute), works identically. Pulumi uses real programming languages (TypeScript/Python) instead of HCL. For most teams, Terraform/OpenTofu is the simplest.

Q Ansible vs Puppet vs Chef?

Ansible has won the config-management space. Puppet/Chef are older, require agents, heavier to operate. For a DomainIndia VPS fleet, stick with Ansible.

Q Can I manage shared cPanel with IaC?

Limited. cPanel has a WHM API, but Terraform providers are community-maintained. For serious automation, go VPS.

Start your IaC journey with a VPS where you have full root access. Order VPS

Was this article helpful?

Your feedback helps us improve our documentation

Still need help? Submit a support ticket