Alibaba Cloud maintains an official Terraform provider — aliyun/alicloud — that's mature enough to treat as a first-class citizen alongside aws and azurerm in a multi-cloud codebase. If you already write Terraform for AWS or Azure, the provider is closer to what you know than the console UI is.

Provider setup

terraform {
  required_providers {
    alicloud = {
      source  = "aliyun/alicloud"
      version = "~> 1.230"
    }
  }
}

provider "alicloud" {
  region = "ap-southeast-1"
  # Credentials via environment: ALICLOUD_ACCESS_KEY / ALICLOUD_SECRET_KEY
  # or better — assume a RAM role and never store long-lived keys locally.
}

Same discipline as any other cloud: credentials come from environment variables or a RAM role assumption, never hardcoded in the .tf files, and the state backend goes into OSS (Object Storage Service) with state locking via Tablestore, not local disk.

terraform {
  backend "oss" {
    bucket = "my-terraform-state"
    prefix = "prod/network"
    region = "ap-southeast-1"
    tablestore_endpoint     = "https://my-locks.ap-southeast-1.ots.aliyuncs.com"
    tablestore_table        = "terraform-lock"
  }
}

A minimal VPC + ECS + SLB module

This is the shape of stack I reach for most often — a private subnet for application instances, a public-facing Server Load Balancer in front, and security groups that only allow traffic through the load balancer.

resource "alicloud_vpc" "main" {
  vpc_name   = "app-vpc"
  cidr_block = "10.0.0.0/16"
}

resource "alicloud_vswitch" "private" {
  vpc_id     = alicloud_vpc.main.id
  cidr_block = "10.0.1.0/24"
  zone_id    = "ap-southeast-1a"
}

resource "alicloud_security_group" "app" {
  name   = "app-sg"
  vpc_id = alicloud_vpc.main.id
}

resource "alicloud_security_group_rule" "allow_lb" {
  type              = "ingress"
  ip_protocol       = "tcp"
  port_range        = "8080/8080"
  security_group_id = alicloud_security_group.app.id
  source_security_group_id = alicloud_security_group.lb.id
}

resource "alicloud_instance" "app" {
  count                      = 2
  instance_name              = "app-${count.index}"
  instance_type              = "ecs.g7.large"
  image_id                   = "ubuntu_22_04_x64_20G_alibase_20240320.vhd"
  vswitch_id                 = alicloud_vswitch.private.id
  security_groups            = [alicloud_security_group.app.id]
  internet_max_bandwidth_out = 0 # no public IP — traffic only via SLB
}

resource "alicloud_slb_load_balancer" "public" {
  load_balancer_name = "app-lb"
  vswitch_id          = alicloud_vswitch.private.id
  address_type        = "internet"
}

Where this diverges from AWS/Azure habits

A few gotchas that cost me time the first time through:

  • Availability zones aren't symmetric across regions. ap-southeast-1a might have full instance-type availability while ap-southeast-1c is missing the family you want. Always check alicloud_zones data source rather than hardcoding a zone.
  • Security group rules default to a flat model, not the fine-grained NACL + security group split you get in AWS. Plan your segmentation with fewer, more purposeful security groups rather than replicating an AWS layout 1:1.
  • The alicloud provider's resource naming doesn't always match the console's naming. alicloud_slb_load_balancer is what the console just calls "SLB instance." Keep the provider docs open while you write your first module.

Running it in CI

The same terraform plan / terraform apply split you'd use anywhere else works cleanly here — the provider behaves like any other in a GitHub Actions or Jenkins pipeline:

- name: Terraform Plan
  run: terraform plan -out=tfplan
  env:
    ALICLOUD_ACCESS_KEY: ${{ secrets.ALICLOUD_ACCESS_KEY }}
    ALICLOUD_SECRET_KEY: ${{ secrets.ALICLOUD_SECRET_KEY }}

Store those as encrypted CI secrets, scope the RAM user tightly to the resources the pipeline actually needs to touch, and rotate the keys on a schedule — the same rules that apply to any cloud credential apply here without exception.

If you're running this alongside AWS or Azure infrastructure in the same repository, the provider aliasing pattern (provider "alicloud" { alias = "sg" }) keeps multi-cloud modules readable instead of turning into a maze of conditionals.