Overview
Deploy a website on Atlas Cloud using Terraform. Two paths:
| Path | Use When | SSL |
|---|---|---|
| HTTP | No domain, quick testing | No |
| HTTPS | Have a domain name | Yes (Traefik + Let’s Encrypt) |
Full example: terraform-examples/vm-website
Prerequisites
- Terraform >= 1.0
- Atlas Cloud account
- SSH key pair
- (Optional) Domain name for HTTPS
Get API Credentials
- Log in to sky.runatlas.is
- Click your profile (top-right)
- Copy API Key and Secret Key
Need help? See API Credentials for detailed instructions.
Quick Start
1. Clone the Example
git clone https://github.com/RunAtlas-is/terraform-examples.git
cd terraform-examples/vm-website2. Configure Variables
Create terraform.tfvars:
cloudstack_api_url = "https://sky.runatlas.is/client/api"
cloudstack_api_key = "your-api-key"
cloudstack_secret_key = "your-secret-key"
ssh_public_key = "ssh-rsa AAAA..."
# For HTTPS (optional)
domain_name = "example.com"
email_address = "you@example.com"3. Deploy
terraform init
terraform apply4. Configure DNS (HTTPS only)
Point your domain A record to the output IP:
terraform output webserver_public_ipSSL certificates are auto-provisioned by Traefik.
HTTP Path (No Domain)
Use this for testing or when you don’t have a domain.
📁 main.tf
terraform {
required_providers {
cloudstack = {
source = "cloudstack/cloudstack"
version = "0.6.0"
}
}
required_version = ">=1.0.0"
}
provider "cloudstack" {
api_url = var.cloudstack_api_url
api_key = var.cloudstack_api_key
secret_key = var.cloudstack_secret_key
}
resource "cloudstack_network" "webserver_network" {
name = "webserver-network"
cidr = "10.1.0.0/24"
network_offering = var.network_offering
zone = var.zone
}
resource "cloudstack_instance" "webserver" {
name = "webserver-vm"
service_offering = var.instance_service_offering
template = var.instance_template
zone = var.zone
network = cloudstack_network.webserver_network.name
user_data = templatefile("${path.module}/cloud-init-http.yaml", {
ssh_public_key = var.ssh_public_key
})
}
resource "cloudstack_ipaddress" "webserver_ip" {
network = cloudstack_network.webserver_network.name
}
resource "cloudstack_port_forward" "webserver_ports" {
for_each = {
http = 80
ssh = 22
}
ip_address_id = cloudstack_ipaddress.webserver_ip.id
forward {
protocol = "tcp"
public_port = each.value
private_port = each.value
virtual_machine_id = cloudstack_instance.webserver.id
}
}
resource "cloudstack_firewall" "ingress" {
ip_address_id = cloudstack_ipaddress.webserver_ip.id
rule{
protocol = "tcp"
start_port = 80
end_port = 80
cidr_list = ["0.0.0.0/0"]
}
rule{
protocol = "tcp"
start_port = 22
end_port = 22
cidr_list = var.ssh_allowed_ips
}
}
resource "cloudstack_egress_firewall" "egress" {
network_id = cloudstack_network.webserver_network.id
rule{
protocol = "tcp"
start_port = 80
end_port = 80
cidr_list = ["0.0.0.0/0"]
}
rule{
protocol = "tcp"
start_port = 443
end_port = 443
cidr_list = ["0.0.0.0/0"]
}
rule{
protocol = "udp"
start_port = 53
end_port = 53
cidr_list = ["0.0.0.0/0"]
}
rule{
protocol = "tcp"
start_port = 53
end_port = 53
cidr_list = ["0.0.0.0/0"]
}
}
output "webserver_public_ip" {
value = cloudstack_ipaddress.webserver_ip.ip_address
}
output "website_url"{
value = "http://${cloudstack_ipaddress.webserver_ip.ip_address}/"
}📁 cloud-init-http.yaml
#cloud-config
package_update: true
packages:
- nginx
ssh_authorized_keys:
- ${ssh_public_key}
write_files:
- path: /var/www/html/index.html
content: |
<!DOCTYPE html>
<html>
<head><title>Hello Atlas</title></head>
<body><h1>Hello from Atlas Cloud!</h1></body>
</html>
permissions: '0644'
runcmd:
- systemctl enable nginx
- systemctl start nginx📁 variables.tf
variable "cloudstack_api_url" {
type = string
sensitive = true
}
variable "cloudstack_api_key" {
type = string
sensitive = true
}
variable "cloudstack_secret_key" {
type = string
sensitive = true
}
variable "ssh_public_key" {
type = string
}
variable "zone"{
type = string
default = "is1"
}
variable "instance_service_offering"{
type = string
default = "Atlas.a4"
}
variable "instance_template"{
type = string
default = "Ubuntu 24.04 LTS"
}
variable "network_offering"{
type = string
default = "DefaultIsolatedNetworkOfferingWithSourceNatService"
}
variable "ssh_allowed_ips"{
type = list(string)
default = ["0.0.0.0/0"]
}HTTPS Path (With Domain)
For production with automatic SSL via Traefik and Let’s Encrypt.
📁 main.tf (HTTPS)
terraform {
required_providers {
cloudstack = {
source = "cloudstack/cloudstack"
version = "0.6.0"
}
}
required_version = ">=1.0.0"
}
provider "cloudstack"{
api_url = var.cloudstack_api_url
api_key = var.cloudstack_api_key
secret_key = var.cloudstack_secret_key
}
resource "cloudstack_network" "webserver_network"{
name = "webserver-network"
cidr = "10.1.0.0/24"
network_offering = var.network_offering
zone = var.zone
}
resource "cloudstack_instance" "webserver"{
name = "webserver-vm"
service_offering = var.instance_service_offering
template = var.instance_template
zone = var.zone
network = cloudstack_network.webserver_network.name
user_data = templatefile("${path.module}/cloud-init-https.yaml", {
ssh_public_key = var.ssh_public_key
domain_name = var.domain_name
email_address = var.email_address
})
}
resource "cloudstack_ipaddress" "webserver_ip"{
network = cloudstack_network.webserver_network.name
}
resource "cloudstack_port_forward" "webserver_ports"{
for_each = {
http = 80
https = 443
ssh = 22
}
ip_address_id = cloudstack_ipaddress.webserver_ip.id
forward{
protocol = "tcp"
public_port = each.value
private_port = each.value
virtual_machine_id = cloudstack_instance.webserver.id
}
}
resource "cloudstack_firewall" "ingress"{
ip_address_id = cloudstack_ipaddress.webserver_ip.id
rule{
protocol = "tcp"
start_port = 80
end_port = 80
cidr_list = ["0.0.0.0/0"]
}
rule{
protocol = "tcp"
start_port = 443
end_port = 443
cidr_list = ["0.0.0.0/0"]
}
rule{
protocol = "tcp"
start_port = 22
end_port = 22
cidr_list = var.ssh_allowed_ips
}
}
resource "cloudstack_egress_firewall" "egress"{
network_id = cloudstack_network.webserver_network.id
rule{
protocol = "tcp"
start_port = 80
end_port = 80
cidr_list = ["0.0.0.0/0"]
}
rule{
protocol = "tcp"
start_port = 443
end_port = 443
cidr_list = ["0.0.0.0/0"]
}
rule{
protocol = "udp"
start_port = 53
end_port = 53
cidr_list = ["0.0.0.0/0"]
}
rule{
protocol = "tcp"
start_port = 53
end_port = 53
cidr_list = ["0.0.0.0/0"]
}
}
output "webserver_public_ip"{
value = cloudstack_ipaddress.webserver_ip.ip_address
}
output "website_url"{
value = "https://${var.domain_name}/"
}📁 cloud-init-https.yaml
#cloud-config
package_update: true
packages:
- docker.io
- docker-compose-v2
ssh_authorized_keys:
- ${ssh_public_key}
write_files:
- path: /var/www/html/index.html
content: |
<!DOCTYPE html>
<html>
<head><title>Hello Atlas</title></head>
<body><h1>Hello from Atlas Cloud (HTTPS)!</h1></body>
</html>
permissions: '0644'
- path: /opt/traefik/docker-compose.yml
content: |
services:
traefik:
image: traefik:v3
command:
- "--providers.docker=true"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
- "--certificatesresolvers.letsencrypt.acme.email=${email_address}"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "letsencrypt:/letsencrypt"
nginx:
image: nginx:alpine
labels:
- "traefik.enable=true"
- "traefik.http.routers.nginx.rule=Host(`${domain_name}`)"
- "traefik.http.routers.nginx.entrypoints=websecure"
- "traefik.http.routers.nginx.tls.certresolver=letsencrypt"
- "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
- "traefik.http.routers.nginx-http.rule=Host(`${domain_name}`)"
- "traefik.http.routers.nginx-http.entrypoints=web"
- "traefik.http.routers.nginx-http.middlewares=redirect-to-https"
volumes:
- "/var/www/html:/usr/share/nginx/html:ro"
volumes:
letsencrypt:
permissions: '0644'
runcmd:
- systemctl enable docker
- systemctl start docker
- cd /opt/traefik && docker compose up -d📁 variables.tf (HTTPS)
variable "cloudstack_api_url" {
type = string
sensitive = true
}
variable "cloudstack_api_key" {
type = string
sensitive = true
}
variable "cloudstack_secret_key" {
type = string
sensitive = true
}
variable "ssh_public_key" {
type = string
}
variable "domain_name" {
type = string
}
variable "email_address" {
type = string
}
variable "zone" {
type = string
default = "is1"
}
variable "instance_service_offering" {
type = string
default = "Atlas.a4"
}
variable "instance_template" {
type = string
default = "Ubuntu 24.04 LTS"
}
variable "network_offering" {
type = string
default = "DefaultIsolatedNetworkOfferingWithSourceNatService"
}
variable "ssh_allowed_ips" {
type = list(string)
default = ["0.0.0.0/0"]
}Verify
# Get the URL
terraform output website_url
# Test HTTP
curl -I http://$(terraform output -raw webserver_public_ip)
# Test HTTPS (after DNS propagation)
curl -I https://your-domain.comCleanup
terraform destroyTroubleshooting
| Issue | Solution |
|---|---|
| SSH refused | Check ssh_allowed_ips includes your IP |
| SSL fails | Wait for DNS propagation (5-30 min) |
| Egress blocked | Verify egress firewall rules exist |
| Container won’t start | SSH in and run docker compose logs |
Next Steps
- Setting up Remote Terraform State - For team collaboration
- Website hosting on a VM - Manual console setup