Design cloud landing zones that provide secure, governed foundations for workloads. Covers account structure, network topology, identity federation, guardrails, logging, and the patterns that enable teams to move fast within secure boundaries.
A landing zone architecture defines the foundational cloud environment that enables consistent, repeatable, and governed deployment of cloud resources across multiple teams, applications, and environments. It matters when an organisation scales beyond a single AWS account or Azure subscription, where ad-hoc resource provisioning leads to sprawl, drift, and operational complexity. A landing zone is the starting point for every new project, ensuring that all new work begins on a pre-configured, policy-enforced, and monitored foundation.
A landing zone is not just infrastructure as code; it is a delivery platform for cloud operations. It establishes a shared, multi-tenant foundation that all teams use as their default environment. The core responsibilities are:
The landing zone is the entry point for every new application, service, or workload. When a team creates a new project, they do not provision resources from scratch. Instead, they “land” into the zone by applying a configuration that declares their intent: environment (dev, staging, prod), application tier (web, api, data), and operational requirements (logging level, retention period, auto-scaling policy).
To create a landing zone, teams start with a core infrastructure repository that defines the baseline. This repository contains configuration files for network, IAM, and foundational services. The configuration is versioned, tested, and deployed through a continuous delivery pipeline.
version: 1.0
name: landing-zone-core
description: Core infrastructure for all cloud projects
parameters:
environment: dev
application: orders-service
region: us-east-1
vpc-cidr: 10.0.0.0/16
enable-logging: true
enable-backup: true
retention-days: 90
resources:
- type: aws::vpc
name: shared-vpc
cidr: 10.0.0.0/16
azs: [us-east-1a, us-east-1b, us-east-1c]
public-subnets:
- cidr: 10.0.1.0/24
availability-zone: us-east-1a
- cidr: 10.0.2.0/24
availability-zone: us-east-1b
private-subnets:
- cidr: 10.0.10.0/24
availability-zone: us-east-1a
- cidr: 10.0.11.0/24
availability-zone: us-east-1b
route-tables:
- name: public-route-table
routes:
- destination: 0.0.0.0/0
target: internet-gateway
- name: private-route-table
routes:
- destination: 10.0.0.0/16
target: local
- destination: 0.0.0.0/0
target: nat-gateway
tags:
Environment: '{{ environment }}'
Application: '{{ application }}'
Owner: '{{ owner }}'
ManagedBy: 'landing-zone'
depends-on: [internet-gateway, nat-gateway]
- type: aws::iam::role
name: app-execution-role
assume-role-policy:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
managed-policies:
- arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
- arn:aws:iam::aws:policy/CloudWatchLogsFullAccess
tags:
Environment: '{{ environment }}'
Application: '{{ application }}'
RoleType: execution
This configuration is applied using a declarative tool such as Terraform, AWS CloudFormation, or Pulumi. The deployment process is automated and idempotent: running the same configuration multiple times produces the same state.
When a team creates a new project, they instantiate the landing zone by passing a project manifest:
{
"application": "user-profile-service",
"environment": "prod",
"region": "eu-west-1",
"tags": {
"Owner": "engineering-team",
"BusinessUnit": "customer-engagement"
},
"services": [
{
"name": "web-api",
"type": "aws::ecs::cluster",
"settings": {
"capacity": 2,
"task-definition": "user-api-task"
}
},
{
"name": "database",
"type": "aws::rds::instance",
"settings": {
"engine": "postgres",
"instance-class": "db.t3.medium",
"backup-retention": 30
}
}
]
}
This manifest is consumed by a landing zone orchestrator—a CI/CD pipeline or a configuration service—that applies the core template and then instantiates the application-specific resources.
Governance is enforced through policy-as-code, where rules are defined independently of infrastructure and applied across all resources. The landing zone uses a policy engine to validate, audit, and enforce configuration.
Policies are evaluated as part of the deployment pipeline. Each policy checks a set of criteria and fails the build if any rule is violated.
# policies/iam-policy.yaml
name: enforce-iam-tags
description: All IAM resources must have Environment and Owner tags
rules:
- resource-type: aws::iam::role
required-tags:
- Environment
- Owner
enforcement: "required"
description: "All roles must have Environment and Owner tags"
- resource-type: aws::iam::policy
required-tags:
- Environment
- Application
enforcement: "recommended"
description: "Policies should include Application tag"
- resource-type: aws::iam::user
required-tags:
- Environment
- Owner
- Department
enforcement: "required"
description: "All users must have Department tag"
# Policies can be evaluated using a tool like:
# aws cloudformation validate-template --template-body file://templates/landing-zone.yaml
# aws config put-remediation-configurations --remediation-configurations file://policies/iam-policy.yaml
A failure in policy enforcement returns a clear error:
{
"error": "Policy Violation",
"policy": "enforce-iam-tags",
"violations": [
{
"resource": "arn:aws:iam::123456789012:role/user-api-role",
"type": "aws::iam::role",
"rule": "required-tags",
"expected": ["Environment", "Owner"],
"actual": ["Environment", "Application"],
"missing": ["Owner"]
},
{
"resource": "arn:aws:iam::123456789012:user/[email protected]",
"type": "aws::iam::user",
"rule": "required-tags",
"expected": ["Environment", "Owner", "Department"],
"actual": ["Environment", "Owner"],
"missing": ["Department"]
}
]
}
This error stops the CI pipeline, prevents deployment, and notifies the responsible team.
The landing zone defines a multi-tiered network architecture with isolated subnets and consistent security rules.
Every new application lands in a shared VPC or a dedicated VPC with consistent naming and tagging. The network layout includes:
Security is enforced through security group templates and network ACLs.
# templates/security-groups.yaml
defaults:
ingress:
- protocol: tcp
port: 80
source: 0.0.0.0/0
description: "HTTP access from internet"
- protocol: tcp
port: 443
source: 0.0.0.0/0
description: "HTTPS access from internet"
egress:
- protocol: all
destination: 0.0.0.0/0
description: "All outbound traffic"
services:
web:
description: "Web frontend"
ingress:
- protocol: tcp
port: 80
source: 0.0.0.0/0
description: "HTTP for web"
- protocol: tcp
port: 443
source: 0.0.0.0/0
description: "HTTPS for web"
- protocol: tcp
port: 22
source: 10.0.1.0/24
description: "SSH from dev team"
egress:
- protocol: tcp
port: 5432
destination: 10.0.10.0/24
description: "Database access"
- protocol: tcp
port: 9000
destination: 10.0.20.0/24
description: "API gateway access"
Security groups are applied at deployment time and verified via a post-deployment validation step:
$ aws ec2 describe-security-groups \
--filters Name=group-name,Values=web-service-sg \
--query 'SecurityGroups[0].IpPermissions[]' \
--output json
[
{
"IpProtocol": "tcp",
"FromPort": 80,
"ToPort": 80,
"IpRanges": [
{
"CidrIp": "0.0.0.0/0",
"Description": "HTTP access from internet"
}
]
},
{
"IpProtocol": "tcp",
"FromPort": 443,
"ToPort": 443,
"IpRanges": [
{
"CidrIp": "0.0.0.0/0",
"Description": "HTTPS access from internet"
}
]
},
{
"IpProtocol": "tcp",
"FromPort": 22,
"ToPort": 22,
"IpRanges": [
{
"CidrIp": "10.0.1.0/24",
"Description": "SSH from dev team"
}
]
}
]
The most common failure in network configuration is incorrect routing between subnets. A service in a private subnet cannot reach a database in another private subnet because the route table lacks a route to the correct CIDR block. This is silent until logs show Connection timed out or Could not resolve host.
The landing zone centralizes identity through federated authentication and role-based access control.
All applications use AWS IAM roles that are assumed by services at runtime. The landing zone defines a role template:
# templates/iam-role-template.yaml
name: "{{ application }}-{{ environment }}-role"
assumed-by: "ecs-tasks.amazonaws.com"
policies:
- inline:
name: "s3-access"
policy-document:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- s3:GetObject
- s3:PutObject
Resource: "arn:aws:s3:::{{ application }}-data/*"
- managed:
arn: "arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy"
- managed:
arn: "arn:aws:iam::aws:policy/SecretsManagerReadWrite"
tags:
Environment: "{{ environment }}"
Application: "{{ application }}"
Tier: "backend"
Owner: "{{ owner }}"
Applications use this role via EC2 instance metadata, AWS STS, or OpenID Connect.
When a task is launched in ECS, it assumes the role:
{
"roleArn": "arn:aws:iam::123456789012:role/user-profile-service-prod-role",
"roleSessionName": "ecs-task-1234567890",
"durationSeconds": 3600
}
The application then uses the credentials to access AWS services.
A common failure is role assumption timeout. The ECS task starts, but the role is not assumed due to a misconfigured trust relationship or STS endpoint.
{
"error": "STSException",
"code": "InvalidIdentityProvider",
"message": "The identity provider does not support the specified role.",
"details": {
"expected": {
"principal": "ecs-tasks.amazonaws.com",
"condition": {
"StringEquals": {
"sts:AssumedRole": "arn:aws:iam::123456789012:role/user-profile-service-prod-role"
}
}
},
"actual": {
"principal": "ecs-tasks.amazonaws.com",
"condition": {
"StringEquals": {
"sts:AssumedRole": "arn:aws:iam::123456789012:role/user-profile-service-dev-role"
}
}
}
}
}
This error is silent until logs show AWS::STS::AssumeRole failed with 400.
The landing zone includes standard observability tooling. Every application is instrumented with:
The landing zone defines a standard logging configuration:
# templates/logging-config.yaml
format: json
level: info
fields:
application: "{{ application }}"
environment: "{{ environment }}"
service: "{{ service }}"
version: "{{ version }}"
host: "{{ hostname }}"
destination:
type: cloudwatch
log-group: "/app/{{ application }}/{{ environment }}"
retention-days: 90
batch-size: 1000
flush-interval: 10s
Applications emit logs in this format:
{
"timestamp": "2023-10-05T12:34:56.789Z",
"level": "info",
"message": "User profile fetched",
"application": "user-profile-service",
"environment": "prod",
"service": "web-api",
"version": "1.2.0",
"host": "ip-10-0-1-10.ec2.internal",
"userId": "u12345",
"durationMs": 142
}
A failure in observability is missing log fields. The logs arrive, but application, environment, and service are missing. The root cause is an inconsistent logging configuration across services.
The landing zone is not a one-time setup. It is a living system, continuously updated with new templates, policies, and tooling. The most valuable insight is that a landing zone is not the infrastructure—it is the process of how teams interact with that infrastructure.
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