Design a secure, scalable cloud foundation. Covers account/subscription structure, networking, identity, guardrails, logging, and the landing zone pattern for AWS and Azure.
A Cloud Landing Zone (CLZ) is a pre-configured, repeatable foundation for deploying cloud workloads at scale. It defines the core infrastructure, security, governance, and operational standards that every team in an enterprise can use to launch and manage cloud applications. It matters when an organisation transitions from ad-hoc cloud deployments to a structured, governed, and scalable cloud practice—typically after initial experimentation, when teams begin to deliver cloud-native services in parallel across multiple domains.
The CLZ begins with a declarative configuration that defines the baseline environment across one or more cloud providers. This configuration is stored in version-controlled repositories, typically using Git, and applied via infrastructure-as-code (IaC) tools such as Terraform, Pulumi, or AWS CloudFormation.
A CLZ configuration consists of a set of modules, each responsible for a distinct domain: networking, identity, security, observability, and operations. These modules are applied in a specific order to ensure consistency and idempotency.
The foundation of any CLZ is a network topology that enables secure, scalable, and performant connectivity between services.
A typical CLZ defines:
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "3.14.0"
name = "prod-core"
cidr = "10.0.0.0/16"
azs = ["us-west-2a", "us-west-2b", "us-west-2c"]
private_subnets = ["10.0.10.0/24", "10.0.20.0/24", "10.0.30.0/24"]
public_subnets = ["10.0.100.0/24", "10.0.110.0/24", "10.0.120.0/24"]
enable_nat_gateway = true
single_nat_gateway = true
enable_dns_hostnames = true
enable_dns_support = true
}
Subnet delegation to route tables is critical. A common failure mode is forgetting to assign the correct route table to a subnet, resulting in instances in private subnets being unreachable from the internet or from other services. Another silent failure occurs when a new subnet is added to the VPC but is not associated with the correct route table, causing traffic to default to the main route table and bypassing custom routing rules.
Central to the CLZ is a unified identity model based on roles, policies, and federated authentication.
The CLZ defines:
app-deployer, devops-admin, data-engineer, security-auditor.iam/policies/roles/{role-name}.json.Example policy for a data-engineer role:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:ListBucket"
],
"Resource": "arn:aws:s3:::data-lake/*"
},
{
"Effect": "Allow",
"Action": [
"glue:GetDatabase",
"glue:GetTable",
"glue:CreateDatabase",
"glue:CreateTable"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"athena:StartQueryExecution",
"athena:GetQueryExecution"
],
"Resource": "*"
}
]
}
Roles are assumed via AWS Security Token Service (STS) or a similar identity provider, with session tokens that include:
aws:userId: the identity of the user or service.aws:sourceIPAddress: the originating IP address.aws:accountId: the account where the role is defined.A common failure mode is incorrect Principal configuration in the role trust policy, leading to failure when a service tries to assume a role. For instance, if a Lambda function in 123456789012 tries to assume a role in 210987654321, but the Principal in the role is set to 123456789012 instead of 210987654321, the assumption fails silently with:
{
"error": "AccessDenied",
"message": "User: arn:aws:iam::123456789012:role/LambdaExecution is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::210987654321:role/ProdDataEngineer"
}
Security in the CLZ is enforced through a combination of policies, tools, and automated checks.
The CLZ defines:
// Example SCP: enforce encryption and tagging
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:CreateVolume",
"ec2:CreateSnapshot"
],
"Resource": "*",
"Condition": {
"StringEquals": {
"ec2:Encrypted": "true"
}
}
},
{
"Effect": "Deny",
"Action": [
"s3:PutObject",
"s3:PutObjectAcl"
],
"Resource": "arn:aws:s3:::data-lake/*",
"Condition": {
"Null": {
"s3:x-amz-meta-project": true
}
}
}
]
}
Security is reinforced by automated scanning tools such as Checkov, Terratest, or custom pipelines that run before every deployment.
The CLZ includes a standard CI/CD pipeline that:
terraform validate.terraform plan and applies changes in a staging environment.checkov -f main.tf -f modules/networking/main.tf and fails the pipeline if any critical or high severity check is missed.A CLZ includes a unified observability stack that enables visibility across all services.
The core observability components are:
A standard otel-collector-config.yaml is included in the CLZ:
receivers:
otlp:
protocols:
grpc:
http:
exporters:
prometheus:
endpoint: "0.0.0.0:8888"
namespace: "cloud_landing_zone"
logging:
loglevel: "info"
awscloudwatch:
region: "us-west-2"
endpoint: "https://monitoring.us-west-2.amazonaws.com"
metric_version: "2010-08-01"
default_dimensions:
Environment: "prod"
Service: "cloud-lz"
processors:
batch:
send_batch_max_size: 1000
timeout: 10s
memory_limiter:
limit_mib: 100
resource:
attributes:
service.name: "cloud-lz"
service.version: "1.0"
deployment.environment: "prod"
extensions:
health_check:
pprof:
endpoint: "0.0.0.0:8889"
zpages:
endpoint: "0.0.0.0:8890"
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch, memory_limiter, resource]
exporters: [awscloudwatch]
metrics:
receivers: [otlp]
processors: [batch, memory_limiter, resource]
exporters: [prometheus]
logs:
receivers: [otlp]
processors: [batch, memory_limiter, resource]
exporters: [logging]
This configuration is deployed via Helm charts or container manifests. A common failure is missing the service.name attribute in the resource processor, resulting in metrics and traces being grouped under a generic unknown service in the monitoring dashboard.
The CLZ defines a set of operational patterns that guide how teams build and run services.
Each service must:
kubectl get pods -L app,env,team,version)./metrics./health endpoint that returns 200 OK on success, with a JSON body: {
"status": "healthy",
"timestamp": "2023-07-12T10:00:00Z",
"details": {
"database": "healthy",
"cache": "healthy",
"dependencies": {
"auth-service": "reachable",
"storage": "reachable"
}
}
}
/ready endpoint that indicates whether the service is ready to serve traffic, returning 200 OK only after startup and initialization.A failure mode is confusing /health and /ready endpoints. A service may report healthy but not be ready to serve requests because it hasn’t loaded its cache or connected to all downstream services. This leads to rolling deployments that fail silently: instances become healthy but still return 500 errors until the cache warms up.
The CLZ also defines a standard Makefile for local development:
.PHONY: build test deploy
build:
docker build -t myapp:latest .
test:
docker run --rm myapp:latest pytest tests/
deploy:
kubectl apply -f k8s/
kubectl rollout status deployment/myapp
dev:
docker-compose up --build
This ensures that every team uses the same workflow. A common mistake is forgetting to run make deploy after make build, leading to new images being built but not deployed.
The CLZ is not static. It evolves through a versioning and change management process.
Each CLZ configuration is versioned using semantic versioning (e.g., v1.2.3). Changes are proposed via pull requests, reviewed, tested in a staging environment, and promoted to production.
A key mechanism is the CLZ version manifest, stored as clz-version.yaml:
version: "1.2.3"
components:
- name: "networking"
version: "3.14.0"
source: "git::https://github.com/enterprise/infrastructure-modules.git//networking"
- name: "iam"
version: "2.0.1"
source: "git::https://github.com/enterprise/iam-policies.git//roles"
- name: "observability"
version: "1.5.0"
source: "helm::https://charts.example.com/observability"
When a new CLZ version is released, teams can update their environment by:
terraform init, then terraform version -upgrade to pull in the new component versions.terraform apply with a plan file that captures the upgrade path.A silent failure occurs when a new version of a component introduces breaking changes but the clz-version.yaml file is not updated, or when the terraform version -upgrade command is omitted. This leads to version drift across environments and inconsistent configurations.
The CLZ is complete when every new team can launch a service using the same core configuration, with minimal onboarding. It is successful when a new service can be deployed end-to-end—code to production—with a single command, and all required policies, network rules, monitoring, and access controls applied automatically.
This page was rewritten on 17 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy