# 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
| Tool | Role | Declarative | Best for |
| Terraform | Provisioning (create VMs, DNS, load balancers) | Yes — desired state | "What infrastructure exists" |
| Ansible | Configuration (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