Multi-Cloud & IaC · Oct 2025 · 22 min read
Terraform on Alibaba Cloud: Building Repeatable Infrastructure as Code
A complete Terraform project on Alibaba Cloud — module structure, state management, CI/CD, drift detection, and the boundary where Terraform ends and configuration management begins.
Part 1 of the Alibaba Cloud Engineering Lab Series.
TL;DR
This isn't "Terraform lets you provision Alibaba Cloud" — that's true but generic. It's how to build repeatable Alibaba Cloud infrastructure with Terraform: a real multi-tier project, module structure, remote state, CI/CD, drift detection, and the boundary where Terraform's job ends and configuration management begins.
Git → Terraform code → plan → state → apply → VPC/ECS/OSS/ACK → Alibaba Cloud
Before the how, the what — three terms this guide leans on:
- Infrastructure as Code (IaC) — describing your cloud infrastructure (networks, servers, databases) in text files instead of clicking through a console. The point isn't just automation — it's that the text file is a single, reviewable, version-controlled source of truth for what your infrastructure is supposed to look like, the same discipline you already apply to application code.
- Terraform provider — a plugin that translates Terraform's generic language into calls against one specific cloud's API.
aliyun/alicloudis the Alibaba Cloud provider; Azure and AWS each have their own. The provider is why the same Terraform workflow (write, plan, apply) works across completely different clouds — only the resource names and fields change. - Terraform state — a file Terraform keeps that records what it already created, so a second
applyknows the difference between "create this new thing" and "this already exists, only two fields changed." Without state, every run would have no memory of previous runs — see Section 06 for why that file has to be handled carefully.
Every resource below is terraform validate-verified against the real aliyun/alicloud provider, not written from memory and assumed correct — the companion repo has the actual module structure, restructured from a single flat file into the modules/environments layout Section 04 describes below.
01 — Why This, and What "Repeatable" Actually Means
Alibaba Cloud is the dominant hyperscaler across China and much of Southeast Asia — a region most Azure- and AWS-trained IaC practitioners have never provisioned into. Alibaba's own Terraform documentation positions it as the IaC mechanism spanning ECS, VPC, OSS, ACK, RAM, PAI, and the rest of the platform — the same breadth Terraform covers on Azure or AWS.
Git
│
▼
Terraform Code
│
terraform plan
│
▼
Terraform State
│
terraform apply
│
┌────────────┼────────────┐
▼ ▼ ▼
VPC ECS OSS
│ │
▼ ▼
Security ACK
Groups
│
└────────────┬────────────┘
▼
Alibaba Cloud
The rest of this article builds toward one concrete deliverable: a production-ready web application, fully reproducible from a Terraform project, not a series of disconnected resource snippets.
02 — Provider Setup: Be Explicit About the Source
The Terraform Registry currently lists two Alibaba Cloud providers — the Alibaba-maintained aliyun/alicloud and a legacy hashicorp/alicloud listing. Ambiguity here causes real init/version problems — always pin the source explicitly, not just the provider short name:
terraform {
required_providers {
alicloud = {
source = "aliyun/alicloud" # the actively Alibaba-maintained provider
version = "~> 1.230"
}
}
required_version = ">= 1.6.0"
}
provider "alicloud" {
region = var.region
}
Credentials via environment variables — Alibaba's documentation recommends environment-based credential configuration for local usage, never hardcoded in a provider block:
export ALICLOUD_ACCESS_KEY="..."
export ALICLOUD_SECRET_KEY="..."
export ALICLOUD_REGION="ap-southeast-1"
Production CI/CD should go further than "environment variables instead of hardcoded" — use short-lived or managed credentials where the pipeline supports them, rather than a long-lived static AccessKey committed to a secrets store.
03 — The Project: A Production-Ready Web Application
Scenario: deploy a real multi-tier web application — VPC, VSwitch, security groups, ECS, OSS, SLB, a RAM role scoped to the app, optional RDS, and outputs a reader can actually reproduce.
Network foundation:
resource "alicloud_vpc" "main" {
vpc_name = "vpc-prod-apac"
cidr_block = "10.10.0.0/16"
}
resource "alicloud_vswitch" "app" {
vswitch_name = "vsw-app-tier"
vpc_id = alicloud_vpc.main.id
cidr_block = "10.10.1.0/24"
zone_id = "ap-southeast-1a"
}
resource "alicloud_vswitch" "data" {
vswitch_name = "vsw-data-tier"
vpc_id = alicloud_vpc.main.id
cidr_block = "10.10.2.0/24"
zone_id = "ap-southeast-1b"
}
A VSwitch is bound to a single zone at creation, unlike an Azure subnet spanning a region — plan zone placement deliberately for availability.
Security groups:
resource "alicloud_security_group" "app_sg" {
security_group_name = "sg-app-tier"
vpc_id = alicloud_vpc.main.id
}
resource "alicloud_security_group_rule" "allow_https" {
type = "ingress"
ip_protocol = "tcp"
nic_type = "intranet"
policy = "accept"
port_range = "443/443"
priority = 1
security_group_id = alicloud_security_group.app_sg.id
cidr_ip = "0.0.0.0/0"
}
Compute:
resource "alicloud_instance" "app_server" {
instance_name = "ecs-app-01"
instance_type = "ecs.g6.large"
image_id = "aliyun_3_x64_20G_alibase_20240628.vhd"
vswitch_id = alicloud_vswitch.app.id
security_groups = [alicloud_security_group.app_sg.id]
internet_max_bandwidth_out = 5
system_disk_category = "cloud_essd"
tags = { environment = "production", managed_by = "terraform" }
}
Storage (OSS):
resource "alicloud_oss_bucket" "app_assets" {
bucket = "app-static-assets-prod"
}
# ACL is a separate resource as of provider 1.220+ — "private" is also
# the bucket's default, but set it explicitly rather than relying on it.
resource "alicloud_oss_bucket_acl" "app_assets" {
bucket = alicloud_oss_bucket.app_assets.bucket
acl = "private"
}
Load balancing:
resource "alicloud_slb_load_balancer" "app_lb" {
load_balancer_name = "slb-app-prod"
vswitch_id = alicloud_vswitch.app.id
load_balancer_spec = "slb.s2.small"
}
resource "alicloud_slb_listener" "https" {
load_balancer_id = alicloud_slb_load_balancer.app_lb.id
backend_port = 443
frontend_port = 443
protocol = "tcp"
bandwidth = 10
}
A RAM role scoped to what the app actually needs — read access to its own OSS bucket, nothing account-wide:
resource "alicloud_ram_role" "app_role" {
role_name = "app-server-role"
assume_role_policy_document = jsonencode({
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = { Service = ["ecs.aliyuncs.com"] }
}]
Version = "1"
})
}
Data tier (optional RDS), scoped to the app-tier CIDR only:
resource "alicloud_db_instance" "primary" {
engine = "MySQL"
engine_version = "8.0"
instance_type = "rds.mysql.s2.large"
instance_storage = 100
vswitch_id = alicloud_vswitch.data.id
instance_name = "rds-prod-primary"
security_ips = ["10.10.1.0/24"]
}
Outputs, so the deployment hands back what a consumer actually needs:
output "load_balancer_ip" {
value = alicloud_slb_load_balancer.app_lb.address
}
output "oss_bucket_name" {
value = alicloud_oss_bucket.app_assets.bucket
}
04 — Repository Structure, and Why It's Shaped This Way
terraform-alibaba/
├── main.tf
├── provider.tf
├── variables.tf
├── outputs.tf
├── versions.tf
├── terraform.tfvars
├── modules/
│ ├── network/
│ ├── ecs/
│ ├── security/
│ └── storage/
└── environments/
├── dev/
├── staging/
└── production/
modules/isolates each infrastructure concern (network, compute, security, storage) so a change to how ECS is provisioned doesn't require touching network code.environments/keeps dev/staging/production as separate root configurations calling the same modules with different variables — the environment differs in scale and CIDR ranges, not in the underlying architecture.versions.tfpins the provider source and Terraform CLI version separately frommain.tf, so a version bump is a one-file, reviewable diff.
This is the difference between "knows Terraform syntax" and "can structure a Terraform project" — the second is what actually gets evaluated in a real engineering review.
05 — The Terraform Lifecycle
WRITE
↓
terraform fmt
↓
terraform validate
↓
terraform plan
↓
CODE REVIEW
↓
terraform apply
↓
STATE
↓
DRIFT DETECTION
↓
UPDATE
Alibaba's documentation covers init, plan, apply, and destroy, with plan previewing changes before they're applied. Two commands worth treating as non-negotiable in a professional workflow, beyond that baseline:
terraform fmt -check # enforce consistent formatting, fail CI if not run
terraform validate # catch syntax/config errors before a plan even runs
Skipping these two doesn't save time — it just moves the failure from a 2-second local check to a slower, more visible CI failure or a bad plan output nobody trusts.
06 — State Management
Why does Terraform need state at all? Because it has to answer one question before every apply: "what did I create previously?"
Terraform configuration
+
Terraform state
+
Actual Alibaba Cloud infrastructure
↓
Terraform determines the difference
Without state, every apply would have no way to distinguish "create this new resource" from "this resource already exists, only these two fields changed." State is the memory that makes incremental, non-destructive updates possible.
What a serious state practice covers:
- Remote state — an OSS bucket, never a local
.tfstatefile, and never committed to Git (it can contain sensitive values in plain text). - State locking — prevents two people running
applysimultaneously from corrupting state; pair the OSS backend with Table Store for lock coordination. - State backup — versioning enabled on the state bucket, so a bad apply's prior state is recoverable.
- Sensitive information — database passwords and similar values that land in state should be sourced from a secrets manager reference, not a plaintext
variabledefault. - Team collaboration — remote state with locking is what makes concurrent team usage safe at all.
- State isolation between environments — dev, staging, and production each need their own state file; one shared state file across environments is how a
terraform destroyin dev takes down production by accident.
terraform {
backend "oss" {
bucket = "terraform-state-prod-apac"
key = "network/terraform.tfstate"
region = "ap-southeast-1"
}
}
07 — Terraform vs. the Alibaba Cloud Console
| Task | Console | Terraform |
|---|---|---|
| One-off VM | Excellent | Overkill |
| Repeat environment | Poor | Excellent |
| Version control | Limited | Excellent |
| Code review | Limited | Excellent |
| Multi-environment | Manual | Excellent |
| Disaster recovery | Manual | Strong |
| Large infrastructure | Difficult | Strong |
| Learning curve | Low | Higher |
Alibaba's own documentation draws essentially this same distinction — Terraform earns its complexity budget at the point where an environment needs to be repeatable, reviewable, or reproduced more than once. A single throwaway test VM doesn't need a Terraform module; a production environment always does.
08 — Modules: From Script to Architecture
module "network" {
source = "./modules/network"
vpc_cidr = var.vpc_cidr
environment = var.environment
}
Resource → Module → Environment is the progression that turns Terraform from a scripting tool into reusable infrastructure architecture. The Registry already has community modules worth knowing about — the alibaba/vpc/alicloud module, for instance, wraps VPC, VSwitch, and route-entry resources into a single reusable call — worth evaluating before writing an equivalent module from scratch.
09 — Multi-Environment Design
Terraform Modules
│
┌──────────┼──────────┐
▼ ▼ ▼
DEV STAGING PROD
│ │ │
▼ ▼ ▼
Alibaba Cloud environments
The difference between environment = "dev" and environment = "production" isn't just a variable value — it's isolation: separate state files, separate VPC CIDR ranges (so they can never accidentally peer), separate credentials with separate permission scopes, and in most orgs, a manual-approval gate on production apply that doesn't exist for dev. Environment isolation is a security and blast-radius boundary, not a naming convention.
10 — CI/CD
Developer
│
▼
Git push
│
▼
CI Pipeline
│
├── terraform fmt
├── terraform validate
├── security scan
└── terraform plan
│
▼
Pull Request
│
Approval
│
▼
terraform apply
│
▼
Alibaba Cloud
This is the step that turns isolated Terraform knowledge into an actual DevOps practice: plan output posted to the pull request for human review, apply gated behind approval, and a security scan (tfsec/checkov) catching a public 0.0.0.0/0 security-group rule or an unencrypted OSS bucket before it merges — the same gate a CI pipeline enforces on application code, applied to infrastructure code.
11 — Drift Detection
The scenario every team eventually hits: someone opens the Alibaba Cloud console and manually changes a security group rule "just to fix something quickly" — port 22 gets opened where Terraform's configuration says it should stay restricted.
Terraform configuration says: Port 443 = allowed, Port 22 = restricted
Actual infrastructure says: Port 22 = open
terraform plan
This single command surfaces the exact drift — Terraform reads the real infrastructure state and diffs it against configuration, and the plan output shows the manual change as something Terraform intends to revert. This is one of the strongest arguments for IaC over console-driven changes: the drift isn't just detected, it's specifically named, in a way a manual audit would take far longer to catch.
12 — What Terraform Doesn't Manage
Terraform is excellent for infrastructure lifecycle management — it is not a full configuration-management system. Alibaba's own Terraform provider documentation is explicit that Terraform manages infrastructure resources, not system-level operational tasks like installing software or managing OS updates.
Terraform
↓
Infrastructure
↓
ECS / VPC / SLB / OSS
↓
Ansible / cloud-init / containers
↓
Application configuration
Knowing this boundary is a maturity signal — a common mistake is trying to force Terraform's null_resource + remote-exec into doing configuration management that a purpose-built tool (Ansible, cloud-init, or a container image) handles far more reliably.
13 — Connecting to the AI Infrastructure Article
This isn't an isolated tutorial — it's the infrastructure layer underneath this series' AI Infrastructure article:
Terraform
│
├── VPC
├── ECS GPU
├── OSS
├── Security
│
▼
Alibaba Cloud Infrastructure
│
▼
PAI
│
├── DSW
├── DLC
└── EAS
│
▼
AI Application
Real-world scenario: deploying an AI application on Alibaba Cloud needs GPU ECS, a VPC with a private subnet, OSS for model storage, PAI, a load balancer, security groups, and a CI/CD pipeline gating all of it. Terraform establishes every piece of that infrastructure layer before PAI's DSW/DLC/EAS ever touches a workload — the two articles are one continuous engineering path, not two separate topics that happen to both mention Alibaba Cloud.
Final Takeaways
The value here isn't "learn a fourth cloud provider's resource names" — it's proving that a well-formed Terraform practice transfers: the resources differ, the naming differs, but modular structure, remote state with locking, environment isolation, CI-gated plan/apply, and drift detection are identical disciplines regardless of which cloud sits above the API.
My recommendation: treat Terraform on Alibaba Cloud exactly like Terraform anywhere else — pin the provider source explicitly, never let state live locally, gate production apply behind review, and stop at the boundary where configuration management should take over. Get that discipline right once, and it ports to the next provider almost unchanged.
GitHub Repository: terraform-alibaba-cloud-lab — the full modular project: network/security/ecs/storage modules composed into a production environment, ready to run.
Reviewed against current Alibaba Cloud Terraform documentation and the Terraform Registry as of September 2026.
Terraform · Alibaba Cloud · Multi-Cloud IaC · State Management · Modules · CI/CD · Drift Detection