# Terraform and OpenTofu on Alibaba Cloud: Getting Started with the Alicloud Provider

> Deploy your first resources on Alibaba Cloud with Terraform or OpenTofu: RAM user credentials, a VPC with a vSwitch, a security group, an ECS instance and an OSS bucket.

- Source: https://www.itwonderlab.com/terraform-alibaba-cloud-getting-started/
- Published: 2026-06-19
- Updated: 2026-06-19
- Author: Javier Ruiz Jiménez (https://www.javierruizjimenez.com/)
- Site: IT Wonder Lab (https://www.itwonderlab.com/)

---

## Alibaba Cloud with Terraform

Terraform and OpenTofu manage [Alibaba Cloud](https://www.itwonderlab.com/alibaba-cloud/) with the `alicloud` [provider](https://www.itwonderlab.com/terraform-provider/), published by Alibaba Cloud (the namespace is `aliyun`). The workflow is the same as for [AWS](https://www.itwonderlab.com/aws-terraform-tutorial-terraform-basics/), [Azure](https://www.itwonderlab.com/terraform-azure-getting-started/) and [Google Cloud](https://www.itwonderlab.com/terraform-gcp-getting-started/): only the provider and the resource names change.

Some names differ from other clouds:

| AWS | Alibaba Cloud |
|---|---|
| [VPC](https://www.itwonderlab.com/aws-vpc/) | VPC (`alicloud_vpc`) |
| Subnet | **vSwitch** (`alicloud_vswitch`), always in one zone |
| [EC2](https://www.itwonderlab.com/aws-ec2/) | **ECS** (`alicloud_instance`) |
| Security group | Security group (`alicloud_security_group`) |
| [S3](https://www.itwonderlab.com/aws-s3/) | **OSS** (`alicloud_oss_bucket`) |
| [IAM](https://www.itwonderlab.com/aws-iam/) user | **RAM** user |
| Key pair | ECS key pair |

Alibaba Cloud runs two separate sites, the international one (alibabacloud.com) and the one for mainland China (aliyun.com). Accounts, regions and available services are not shared, so create the account on the site that matches your use.

### Credentials

Do not use the AccessKey of the account owner, which has full access. Create a **RAM user** with programmatic access, give it only the policies you need (for this tutorial ECS, VPC and OSS), and create an AccessKey for it. The provider reads these environment variables:

```shell
$ export ALIBABA_CLOUD_ACCESS_KEY_ID="<AccessKey ID>"
$ export ALIBABA_CLOUD_ACCESS_KEY_SECRET="<AccessKey Secret>"
$ export ALIBABA_CLOUD_REGION="eu-central-1"
```

The provider also supports a shared credentials file with profiles, an ECS instance role, and assuming a RAM role. For pipelines prefer a role to long-lived keys, and keep secrets out of the code (see [secrets management](https://www.itwonderlab.com/terraform-secrets-management/)).

### Provider

```hcl title="providers.tf"
terraform {
  required_version = ">= 1.6"

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

provider "alicloud" {
  # access key, secret and region are read from ALIBABA_CLOUD_* variables
}

variable "instance_type" {
  type        = string
  default     = "ecs.n4.large"
  description = "ECS instance type. Availability depends on the region and zone"
}

variable "admin_cidr" {
  type        = string
  description = "Address range that can connect with SSH, for example your public IP as x.x.x.x/32"
}

variable "ssh_public_key_path" {
  type    = string
  default = "~/.ssh/id_ed25519.pub"
}
```

The version constraint `~> 1.293` accepts any 1.x release from 1.293. The provider has a 2.0 version in beta that is not selected by this constraint.

### A VPC and a vSwitch

A vSwitch lives in one zone of the region, so look up a zone that offers the instance type you want:

```hcl title="network.tf"
data "alicloud_zones" "main" {
  available_resource_creation = "VSwitch"
  available_instance_type     = var.instance_type
}

resource "alicloud_vpc" "main" {
  vpc_name   = "ditwl-demo"
  cidr_block = "10.0.0.0/16"
}

resource "alicloud_vswitch" "main" {
  vswitch_name = "ditwl-demo"
  vpc_id       = alicloud_vpc.main.id
  cidr_block   = "10.0.1.0/24"
  zone_id      = data.alicloud_zones.main.zones[0].id
}

resource "alicloud_security_group" "web" {
  security_group_name = "ditwl-demo-web"
  vpc_id              = alicloud_vpc.main.id
}

resource "alicloud_security_group_rule" "ssh" {
  type              = "ingress"
  ip_protocol       = "tcp"
  nic_type          = "intranet"
  policy            = "accept"
  port_range        = "22/22"
  priority          = 1
  security_group_id = alicloud_security_group.web.id
  cidr_ip           = var.admin_cidr
}
```

Notes:

- `nic_type = "intranet"` is the value used for security groups of a VPC.
- `port_range` is written `from/to`, so a single port is `22/22`.
- `priority` goes from 1 (highest) to 100. When rules conflict, the one with the lower number wins.

### An ECS instance

Use a data source for the image, and an ECS key pair for SSH instead of a password:

```hcl title="ecs.tf"
data "alicloud_images" "ubuntu" {
  owners      = "system"
  name_regex  = "^ubuntu_22_04_x64"
  most_recent = true
}

resource "alicloud_ecs_key_pair" "main" {
  key_pair_name = "ditwl-demo"
  public_key    = file(pathexpand(var.ssh_public_key_path))
}

resource "alicloud_instance" "web" {
  instance_name              = "ditwl-demo-web"
  instance_type              = var.instance_type
  image_id                   = data.alicloud_images.ubuntu.images[0].id
  vswitch_id                 = alicloud_vswitch.main.id
  security_groups            = [alicloud_security_group.web.id]
  system_disk_category       = "cloud_essd"
  key_name                   = alicloud_ecs_key_pair.main.key_pair_name
  internet_max_bandwidth_out = 5
}

output "web_public_ip" {
  value = alicloud_instance.web.public_ip
}
```

`internet_max_bandwidth_out` is the outgoing bandwidth in Mbps, and a value above 0 gives the instance a public IP address, with the bandwidth charged. Set it to `0` for an instance that must stay private. If the apply fails because the instance type or the disk category is not available in the zone, choose another `instance_type` (the zones data source already filters by it) or a different `system_disk_category`. The image name filter is a regular expression: check that it returns an image with `plan` before you apply, because image names change over time.

### An OSS bucket

OSS bucket names are unique across all users, so make yours specific. The ACL is a separate resource:

```hcl title="storage.tf"
resource "alicloud_oss_bucket" "data" {
  bucket = "ditwl-demo-data-change-me"
}

resource "alicloud_oss_bucket_acl" "data" {
  bucket = alicloud_oss_bucket.data.bucket
  acl    = "private"
}
```

### Run it

```shell
$ tofu init
$ tofu plan -var admin_cidr=203.0.113.25/32
$ tofu apply -var admin_cidr=203.0.113.25/32
$ tofu destroy -var admin_cidr=203.0.113.25/32
```

Use your own public IP in `admin_cidr` and use `terraform` instead of `tofu` if you prefer HashiCorp Terraform. Connect with `ssh root@<public ip>`: the default user of the system images is `root`, so a more careful setup creates another user with [cloud-init](https://www.itwonderlab.com/cloud-init/) and disables root login. For remote state use a [backend](https://www.itwonderlab.com/terraform-backend/) of your choice.

Read next [project structure](https://www.itwonderlab.com/terraform-project-structure/) and [best practices](https://www.itwonderlab.com/terraform-best-practices/).
