Ops.Infrastructure -- Containers, Storage, Certificates, DNS
"The certificate expired at 3 AM. The renewal was manual. The person who knew how to renew it left in October."
The Problem
Infrastructure configuration lives in at least five places:
- Dockerfiles -- scattered across repositories, each with slightly different base images, hardcoded memory limits, and copy-pasted multi-stage build patterns.
- Storage provisioning -- done through a cloud web console by someone with admin access. The settings are not version-controlled.
- TLS certificates -- managed by "the infrastructure person." Renewals are calendar reminders. When the calendar reminder fails, the certificate expires.
- DNS records -- created in a domain registrar's web UI. Nobody knows which records point where without logging in.
- CDN configuration -- set up once during initial deployment and never touched again because nobody remembers how.
The Infrastructure DSL eliminates all five. You declare C# attributes. dotnet build generates every artifact: Dockerfiles, docker-compose files, Kubernetes manifests, Terraform modules, cert-manager certificates, DNS records. You never write YAML, HCL, or Dockerfiles by hand. Change an attribute, rebuild, and every infrastructure artifact regenerates consistently.
Each of these is a critical operational concern. Each is managed differently. None of them are connected to the application code that depends on them. When the OrderService needs PostgreSQL, the connection string is in a config file, but the PostgreSQL storage provisioning (size, redundancy, encryption) is in a Terraform file that lives in a different repository maintained by a different team.
The Infrastructure DSL collapses these five concerns into typed attributes on the application classes that need them. The source generator emits docker-compose files, Dockerfiles, Terraform modules, Kubernetes manifests, and cert-manager resources. Everything is generated. Nothing is manually configured.
This DSL operates in two tiers: Container (Docker Compose, local development) and Cloud (Terraform, Kubernetes, production). There is no InProcess tier because infrastructure is inherently about external resources.
ContainerSpec
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class ContainerSpecAttribute : Attribute
{
public ContainerSpecAttribute(string name, string image) { }
/// <summary>Image tag. Defaults to "latest".</summary>
public string Tag { get; init; } = "latest";
/// <summary>Port mappings in "host:container" format.</summary>
public string[] Ports { get; init; } = Array.Empty<string>();
/// <summary>CPU limit (e.g., "0.5" for half a core).</summary>
public string? CpuLimit { get; init; }
/// <summary>Memory limit (e.g., "512m", "1g").</summary>
public string? MemoryLimit { get; init; }
/// <summary>Volume mounts in "host:container" format.</summary>
public string[] Volumes { get; init; } = Array.Empty<string>();
/// <summary>Environment variables in "KEY=VALUE" format.</summary>
public string[] Environment { get; init; } = Array.Empty<string>();
/// <summary>Container depends on these other container names.</summary>
public string[] DependsOn { get; init; } = Array.Empty<string>();
/// <summary>Health check command.</summary>
public string? HealthCheck { get; init; }
/// <summary>Restart policy: no, always, on-failure, unless-stopped.</summary>
public string RestartPolicy { get; init; } = "unless-stopped";
/// <summary>Network names this container joins.</summary>
public string[] Networks { get; init; } = Array.Empty<string>();
}[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class ContainerSpecAttribute : Attribute
{
public ContainerSpecAttribute(string name, string image) { }
/// <summary>Image tag. Defaults to "latest".</summary>
public string Tag { get; init; } = "latest";
/// <summary>Port mappings in "host:container" format.</summary>
public string[] Ports { get; init; } = Array.Empty<string>();
/// <summary>CPU limit (e.g., "0.5" for half a core).</summary>
public string? CpuLimit { get; init; }
/// <summary>Memory limit (e.g., "512m", "1g").</summary>
public string? MemoryLimit { get; init; }
/// <summary>Volume mounts in "host:container" format.</summary>
public string[] Volumes { get; init; } = Array.Empty<string>();
/// <summary>Environment variables in "KEY=VALUE" format.</summary>
public string[] Environment { get; init; } = Array.Empty<string>();
/// <summary>Container depends on these other container names.</summary>
public string[] DependsOn { get; init; } = Array.Empty<string>();
/// <summary>Health check command.</summary>
public string? HealthCheck { get; init; }
/// <summary>Restart policy: no, always, on-failure, unless-stopped.</summary>
public string RestartPolicy { get; init; } = "unless-stopped";
/// <summary>Network names this container joins.</summary>
public string[] Networks { get; init; } = Array.Empty<string>();
}Every property that matters for a container is a named parameter. No more editing YAML indentation by hand.
StorageSpec
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class StorageSpecAttribute : Attribute
{
public StorageSpecAttribute(string name, StorageKind kind) { }
/// <summary>Storage capacity (e.g., "50Gi", "100Gb").</summary>
public string Capacity { get; init; } = "10Gi";
/// <summary>Redundancy model.</summary>
public StorageRedundancy Redundancy { get; init; }
= StorageRedundancy.LocallyRedundant;
/// <summary>Encryption at rest.</summary>
public bool Encryption { get; init; } = true;
/// <summary>Backup schedule in cron format. Null = no backup.</summary>
public string? BackupSchedule { get; init; }
/// <summary>Retention period for backups (e.g., "30d", "1y").</summary>
public string? RetentionPeriod { get; init; }
/// <summary>IOPS limit. 0 = default/unlimited.</summary>
public int Iops { get; init; } = 0;
/// <summary>Storage tier (Standard, Premium, Archive).</summary>
public string Tier { get; init; } = "Standard";
}
public enum StorageKind
{
Blob,
FileShare,
Database,
Queue,
Table
}
public enum StorageRedundancy
{
LocallyRedundant,
ZoneRedundant,
GeoRedundant,
ReadAccessGeoRedundant
}[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class StorageSpecAttribute : Attribute
{
public StorageSpecAttribute(string name, StorageKind kind) { }
/// <summary>Storage capacity (e.g., "50Gi", "100Gb").</summary>
public string Capacity { get; init; } = "10Gi";
/// <summary>Redundancy model.</summary>
public StorageRedundancy Redundancy { get; init; }
= StorageRedundancy.LocallyRedundant;
/// <summary>Encryption at rest.</summary>
public bool Encryption { get; init; } = true;
/// <summary>Backup schedule in cron format. Null = no backup.</summary>
public string? BackupSchedule { get; init; }
/// <summary>Retention period for backups (e.g., "30d", "1y").</summary>
public string? RetentionPeriod { get; init; }
/// <summary>IOPS limit. 0 = default/unlimited.</summary>
public int Iops { get; init; } = 0;
/// <summary>Storage tier (Standard, Premium, Archive).</summary>
public string Tier { get; init; } = "Standard";
}
public enum StorageKind
{
Blob,
FileShare,
Database,
Queue,
Table
}
public enum StorageRedundancy
{
LocallyRedundant,
ZoneRedundant,
GeoRedundant,
ReadAccessGeoRedundant
}CertificateSpec
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Assembly,
AllowMultiple = true)]
public sealed class CertificateSpecAttribute : Attribute
{
public CertificateSpecAttribute(string domain) { }
/// <summary>Certificate provider.</summary>
public CertificateProvider Provider { get; init; }
= CertificateProvider.LetsEncrypt;
/// <summary>Days before expiry to trigger renewal.</summary>
public int RenewBeforeDays { get; init; } = 30;
/// <summary>Subject alternative names.</summary>
public string[] SubjectAlternativeNames { get; init; }
= Array.Empty<string>();
/// <summary>Key algorithm (RSA2048, RSA4096, ECDSA256, ECDSA384).</summary>
public string KeyAlgorithm { get; init; } = "RSA2048";
}
public enum CertificateProvider
{
LetsEncrypt,
ACM,
AzureKeyVault,
SelfSigned,
Custom
}[AttributeUsage(AttributeTargets.Class | AttributeTargets.Assembly,
AllowMultiple = true)]
public sealed class CertificateSpecAttribute : Attribute
{
public CertificateSpecAttribute(string domain) { }
/// <summary>Certificate provider.</summary>
public CertificateProvider Provider { get; init; }
= CertificateProvider.LetsEncrypt;
/// <summary>Days before expiry to trigger renewal.</summary>
public int RenewBeforeDays { get; init; } = 30;
/// <summary>Subject alternative names.</summary>
public string[] SubjectAlternativeNames { get; init; }
= Array.Empty<string>();
/// <summary>Key algorithm (RSA2048, RSA4096, ECDSA256, ECDSA384).</summary>
public string KeyAlgorithm { get; init; } = "RSA2048";
}
public enum CertificateProvider
{
LetsEncrypt,
ACM,
AzureKeyVault,
SelfSigned,
Custom
}DnsRecord
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Assembly,
AllowMultiple = true)]
public sealed class DnsRecordAttribute : Attribute
{
public DnsRecordAttribute(string name, DnsRecordType type, string value) { }
/// <summary>Time to live in seconds.</summary>
public int Ttl { get; init; } = 300;
/// <summary>Priority (for MX and SRV records).</summary>
public int Priority { get; init; } = 0;
/// <summary>Weight (for SRV records).</summary>
public int Weight { get; init; } = 0;
/// <summary>Whether this record is managed by external DNS controller.</summary>
public bool ExternalDnsManaged { get; init; } = false;
}
public enum DnsRecordType
{
A, AAAA, CNAME, TXT, MX, SRV, NS, CAA
}[AttributeUsage(AttributeTargets.Class | AttributeTargets.Assembly,
AllowMultiple = true)]
public sealed class DnsRecordAttribute : Attribute
{
public DnsRecordAttribute(string name, DnsRecordType type, string value) { }
/// <summary>Time to live in seconds.</summary>
public int Ttl { get; init; } = 300;
/// <summary>Priority (for MX and SRV records).</summary>
public int Priority { get; init; } = 0;
/// <summary>Weight (for SRV records).</summary>
public int Weight { get; init; } = 0;
/// <summary>Whether this record is managed by external DNS controller.</summary>
public bool ExternalDnsManaged { get; init; } = false;
}
public enum DnsRecordType
{
A, AAAA, CNAME, TXT, MX, SRV, NS, CAA
}CdnEndpoint
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class CdnEndpointAttribute : Attribute
{
public CdnEndpointAttribute(string origin) { }
/// <summary>Cache duration (e.g., "1h", "7d", "30d").</summary>
public string CacheDuration { get; init; } = "1d";
/// <summary>Purge CDN cache on every deployment.</summary>
public bool PurgeOnDeploy { get; init; } = true;
/// <summary>Custom domain for the CDN endpoint.</summary>
public string? CustomDomain { get; init; }
/// <summary>Compression enabled.</summary>
public bool Compression { get; init; } = true;
/// <summary>WAF enabled on the CDN edge.</summary>
public bool WafEnabled { get; init; } = false;
/// <summary>Cache key query string behavior: Include, Exclude, IgnoreAll.</summary>
public string QueryStringBehavior { get; init; } = "Include";
}[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class CdnEndpointAttribute : Attribute
{
public CdnEndpointAttribute(string origin) { }
/// <summary>Cache duration (e.g., "1h", "7d", "30d").</summary>
public string CacheDuration { get; init; } = "1d";
/// <summary>Purge CDN cache on every deployment.</summary>
public bool PurgeOnDeploy { get; init; } = true;
/// <summary>Custom domain for the CDN endpoint.</summary>
public string? CustomDomain { get; init; }
/// <summary>Compression enabled.</summary>
public bool Compression { get; init; } = true;
/// <summary>WAF enabled on the CDN edge.</summary>
public bool WafEnabled { get; init; } = false;
/// <summary>Cache key query string behavior: Include, Exclude, IgnoreAll.</summary>
public string QueryStringBehavior { get; init; } = "Include";
}Usage: OrderService Infrastructure
// --- Containers ---
[ContainerSpec("order-api", "order-service",
Tag = "#{Version}#",
Ports = new[] { "8080:80", "8443:443" },
CpuLimit = "1.0",
MemoryLimit = "1g",
HealthCheck = "curl -f http://localhost:80/health || exit 1",
Networks = new[] { "order-net", "shared-net" },
DependsOn = new[] { "order-db", "order-broker" })]
[ContainerSpec("order-db", "postgres",
Tag = "16-alpine",
Ports = new[] { "5432:5432" },
CpuLimit = "0.5",
MemoryLimit = "512m",
Volumes = new[] { "order-db-data:/var/lib/postgresql/data" },
Environment = new[] {
"POSTGRES_DB=orders",
"POSTGRES_USER=order_svc",
"POSTGRES_PASSWORD=#{DbPassword}#" },
Networks = new[] { "order-net" })]
[ContainerSpec("order-broker", "rabbitmq",
Tag = "3-management-alpine",
Ports = new[] { "5672:5672", "15672:15672" },
CpuLimit = "0.5",
MemoryLimit = "256m",
Networks = new[] { "order-net", "shared-net" })]
// --- Storage ---
[StorageSpec("order-db-storage", StorageKind.Database,
Capacity = "100Gi",
Redundancy = StorageRedundancy.ZoneRedundant,
Encryption = true,
BackupSchedule = "0 2 * * *",
RetentionPeriod = "30d",
Tier = "Premium",
Iops = 3000)]
[StorageSpec("order-attachments", StorageKind.Blob,
Capacity = "500Gi",
Redundancy = StorageRedundancy.GeoRedundant,
Encryption = true)]
// --- TLS ---
[CertificateSpec("orders.example.com",
Provider = CertificateProvider.LetsEncrypt,
RenewBeforeDays = 30,
SubjectAlternativeNames = new[] { "api.orders.example.com" },
KeyAlgorithm = "ECDSA256")]
// --- DNS ---
[DnsRecord("orders.example.com", DnsRecordType.A, "#{LoadBalancerIp}#",
Ttl = 300)]
[DnsRecord("api.orders.example.com", DnsRecordType.CNAME,
"orders.example.com", Ttl = 300)]
[DnsRecord("orders.example.com", DnsRecordType.TXT,
"v=spf1 include:_spf.example.com ~all", Ttl = 3600)]
[DnsRecord("orders.example.com", DnsRecordType.CAA,
"0 issue \"letsencrypt.org\"", Ttl = 3600)]
// --- CDN ---
[CdnEndpoint("orders.example.com",
CacheDuration = "7d",
PurgeOnDeploy = true,
CustomDomain = "static.orders.example.com",
Compression = true,
WafEnabled = true)]
public partial class OrderServiceInfrastructure { }// --- Containers ---
[ContainerSpec("order-api", "order-service",
Tag = "#{Version}#",
Ports = new[] { "8080:80", "8443:443" },
CpuLimit = "1.0",
MemoryLimit = "1g",
HealthCheck = "curl -f http://localhost:80/health || exit 1",
Networks = new[] { "order-net", "shared-net" },
DependsOn = new[] { "order-db", "order-broker" })]
[ContainerSpec("order-db", "postgres",
Tag = "16-alpine",
Ports = new[] { "5432:5432" },
CpuLimit = "0.5",
MemoryLimit = "512m",
Volumes = new[] { "order-db-data:/var/lib/postgresql/data" },
Environment = new[] {
"POSTGRES_DB=orders",
"POSTGRES_USER=order_svc",
"POSTGRES_PASSWORD=#{DbPassword}#" },
Networks = new[] { "order-net" })]
[ContainerSpec("order-broker", "rabbitmq",
Tag = "3-management-alpine",
Ports = new[] { "5672:5672", "15672:15672" },
CpuLimit = "0.5",
MemoryLimit = "256m",
Networks = new[] { "order-net", "shared-net" })]
// --- Storage ---
[StorageSpec("order-db-storage", StorageKind.Database,
Capacity = "100Gi",
Redundancy = StorageRedundancy.ZoneRedundant,
Encryption = true,
BackupSchedule = "0 2 * * *",
RetentionPeriod = "30d",
Tier = "Premium",
Iops = 3000)]
[StorageSpec("order-attachments", StorageKind.Blob,
Capacity = "500Gi",
Redundancy = StorageRedundancy.GeoRedundant,
Encryption = true)]
// --- TLS ---
[CertificateSpec("orders.example.com",
Provider = CertificateProvider.LetsEncrypt,
RenewBeforeDays = 30,
SubjectAlternativeNames = new[] { "api.orders.example.com" },
KeyAlgorithm = "ECDSA256")]
// --- DNS ---
[DnsRecord("orders.example.com", DnsRecordType.A, "#{LoadBalancerIp}#",
Ttl = 300)]
[DnsRecord("api.orders.example.com", DnsRecordType.CNAME,
"orders.example.com", Ttl = 300)]
[DnsRecord("orders.example.com", DnsRecordType.TXT,
"v=spf1 include:_spf.example.com ~all", Ttl = 3600)]
[DnsRecord("orders.example.com", DnsRecordType.CAA,
"0 issue \"letsencrypt.org\"", Ttl = 3600)]
// --- CDN ---
[CdnEndpoint("orders.example.com",
CacheDuration = "7d",
PurgeOnDeploy = true,
CustomDomain = "static.orders.example.com",
Compression = true,
WafEnabled = true)]
public partial class OrderServiceInfrastructure { }Thirteen attributes. The entire infrastructure footprint for the OrderService. Containers, database, message broker, storage, TLS, DNS, and CDN -- all declared on one class, all version-controlled, all compiler-validated.
docker-compose.generated.yaml
# <auto-generated from [ContainerSpec] attributes on OrderServiceInfrastructure>
version: "3.8"
services:
order-api:
image: order-service:latest
ports:
- "8080:80"
- "8443:443"
deploy:
resources:
limits:
cpus: "1.0"
memory: 1g
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:80/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
restart: unless-stopped
depends_on:
order-db:
condition: service_healthy
order-broker:
condition: service_started
networks:
- order-net
- shared-net
order-db:
image: postgres:16-alpine
ports:
- "5432:5432"
deploy:
resources:
limits:
cpus: "0.5"
memory: 512m
environment:
POSTGRES_DB: orders
POSTGRES_USER: order_svc
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- order-db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U order_svc -d orders"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
networks:
- order-net
order-broker:
image: rabbitmq:3-management-alpine
ports:
- "5672:5672"
- "15672:15672"
deploy:
resources:
limits:
cpus: "0.5"
memory: 256m
healthcheck:
test: ["CMD-SHELL", "rabbitmq-diagnostics -q ping"]
interval: 15s
timeout: 10s
retries: 5
restart: unless-stopped
networks:
- order-net
- shared-net
volumes:
order-db-data:
driver: local
networks:
order-net:
driver: bridge
internal: true
shared-net:
driver: bridge# <auto-generated from [ContainerSpec] attributes on OrderServiceInfrastructure>
version: "3.8"
services:
order-api:
image: order-service:latest
ports:
- "8080:80"
- "8443:443"
deploy:
resources:
limits:
cpus: "1.0"
memory: 1g
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:80/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
restart: unless-stopped
depends_on:
order-db:
condition: service_healthy
order-broker:
condition: service_started
networks:
- order-net
- shared-net
order-db:
image: postgres:16-alpine
ports:
- "5432:5432"
deploy:
resources:
limits:
cpus: "0.5"
memory: 512m
environment:
POSTGRES_DB: orders
POSTGRES_USER: order_svc
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- order-db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U order_svc -d orders"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
networks:
- order-net
order-broker:
image: rabbitmq:3-management-alpine
ports:
- "5672:5672"
- "15672:15672"
deploy:
resources:
limits:
cpus: "0.5"
memory: 256m
healthcheck:
test: ["CMD-SHELL", "rabbitmq-diagnostics -q ping"]
interval: 15s
timeout: 10s
retries: 5
restart: unless-stopped
networks:
- order-net
- shared-net
volumes:
order-db-data:
driver: local
networks:
order-net:
driver: bridge
internal: true
shared-net:
driver: bridgeEvery container has resource limits (because the attribute requires them or the analyzer fires). The database has a health check auto-generated from the image type. The #{DbPassword}# template variable is replaced with a ${DB_PASSWORD} docker-compose variable reference. Networks are declared and internal: true is set for the database network to prevent accidental external access.
Dockerfile.generated
# <auto-generated from [ContainerSpec("order-api")] attributes>
# Multi-stage build for order-service
FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine AS build
WORKDIR /src
COPY ["OrderService/OrderService.csproj", "OrderService/"]
RUN dotnet restore "OrderService/OrderService.csproj"
COPY . .
WORKDIR "/src/OrderService"
RUN dotnet publish -c Release -o /app/publish \
--no-restore \
/p:UseAppHost=false
FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine AS runtime
WORKDIR /app
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
COPY --from=build /app/publish .
EXPOSE 80
EXPOSE 443
HEALTHCHECK --interval=30s --timeout=10s --retries=3 --start-period=40s \
CMD curl -f http://localhost:80/health || exit 1
ENTRYPOINT ["dotnet", "OrderService.dll"]# <auto-generated from [ContainerSpec("order-api")] attributes>
# Multi-stage build for order-service
FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine AS build
WORKDIR /src
COPY ["OrderService/OrderService.csproj", "OrderService/"]
RUN dotnet restore "OrderService/OrderService.csproj"
COPY . .
WORKDIR "/src/OrderService"
RUN dotnet publish -c Release -o /app/publish \
--no-restore \
/p:UseAppHost=false
FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine AS runtime
WORKDIR /app
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
COPY --from=build /app/publish .
EXPOSE 80
EXPOSE 443
HEALTHCHECK --interval=30s --timeout=10s --retries=3 --start-period=40s \
CMD curl -f http://localhost:80/health || exit 1
ENTRYPOINT ["dotnet", "OrderService.dll"]The Dockerfile is generated with security defaults: non-root user, alpine base, multi-stage build, no unnecessary layers. The health check mirrors the HealthCheck property from the attribute.
terraform/infra/main.tf
# <auto-generated from [ContainerSpec], [StorageSpec], [CertificateSpec],
# [DnsRecord], [CdnEndpoint] attributes on OrderServiceInfrastructure>
terraform {
required_version = ">= 1.5"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.80"
}
}
}
# --- Storage: order-db-storage (Database, ZoneRedundant) ---
resource "azurerm_postgresql_flexible_server" "order_db" {
name = "order-db-storage"
resource_group_name = var.resource_group_name
location = var.location
sku_name = "GP_Standard_D2s_v3"
storage_mb = 102400
backup_retention_days = 30
geo_redundant_backup_enabled = false
zone = "1"
high_availability {
mode = "ZoneRedundant"
standby_availability_zone = "2"
}
authentication {
active_directory_auth_enabled = true
password_auth_enabled = false
}
tags = var.tags
}
resource "azurerm_postgresql_flexible_server_database" "orders" {
name = "orders"
server_id = azurerm_postgresql_flexible_server.order_db.id
charset = "UTF8"
collation = "en_US.utf8"
}
# --- Storage: order-attachments (Blob, GeoRedundant) ---
resource "azurerm_storage_account" "order_attachments" {
name = "orderattachments"
resource_group_name = var.resource_group_name
location = var.location
account_tier = "Standard"
account_replication_type = "GRS"
blob_properties {
versioning_enabled = true
}
min_tls_version = "TLS1_2"
tags = var.tags
}
resource "azurerm_storage_container" "order_attachments_container" {
name = "attachments"
storage_account_name = azurerm_storage_account.order_attachments.name
container_access_type = "private"
}
# --- DNS Records ---
resource "azurerm_dns_a_record" "orders" {
name = "orders"
zone_name = var.dns_zone_name
resource_group_name = var.resource_group_name
ttl = 300
records = [var.load_balancer_ip]
}
resource "azurerm_dns_cname_record" "api_orders" {
name = "api.orders"
zone_name = var.dns_zone_name
resource_group_name = var.resource_group_name
ttl = 300
record = "orders.example.com"
}
resource "azurerm_dns_txt_record" "orders_spf" {
name = "orders"
zone_name = var.dns_zone_name
resource_group_name = var.resource_group_name
ttl = 3600
record {
value = "v=spf1 include:_spf.example.com ~all"
}
}
resource "azurerm_dns_caa_record" "orders_caa" {
name = "orders"
zone_name = var.dns_zone_name
resource_group_name = var.resource_group_name
ttl = 3600
record {
flags = 0
tag = "issue"
value = "letsencrypt.org"
}
}
# --- CDN ---
resource "azurerm_cdn_profile" "order_cdn" {
name = "order-cdn"
resource_group_name = var.resource_group_name
location = "global"
sku = "Standard_Microsoft"
tags = var.tags
}
resource "azurerm_cdn_endpoint" "order_cdn_endpoint" {
name = "order-cdn-endpoint"
profile_name = azurerm_cdn_profile.order_cdn.name
resource_group_name = var.resource_group_name
location = "global"
origin {
name = "order-origin"
host_name = "orders.example.com"
}
is_compression_enabled = true
content_types_to_compress = [
"text/html", "text/css", "application/javascript",
"application/json", "image/svg+xml"
]
querystring_caching_behaviour = "UseQueryString"
tags = var.tags
}
# --- Variables ---
variable "resource_group_name" { type = string }
variable "location" { type = string }
variable "dns_zone_name" { type = string }
variable "load_balancer_ip" { type = string }
variable "tags" {
type = map(string)
default = {}
}# <auto-generated from [ContainerSpec], [StorageSpec], [CertificateSpec],
# [DnsRecord], [CdnEndpoint] attributes on OrderServiceInfrastructure>
terraform {
required_version = ">= 1.5"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.80"
}
}
}
# --- Storage: order-db-storage (Database, ZoneRedundant) ---
resource "azurerm_postgresql_flexible_server" "order_db" {
name = "order-db-storage"
resource_group_name = var.resource_group_name
location = var.location
sku_name = "GP_Standard_D2s_v3"
storage_mb = 102400
backup_retention_days = 30
geo_redundant_backup_enabled = false
zone = "1"
high_availability {
mode = "ZoneRedundant"
standby_availability_zone = "2"
}
authentication {
active_directory_auth_enabled = true
password_auth_enabled = false
}
tags = var.tags
}
resource "azurerm_postgresql_flexible_server_database" "orders" {
name = "orders"
server_id = azurerm_postgresql_flexible_server.order_db.id
charset = "UTF8"
collation = "en_US.utf8"
}
# --- Storage: order-attachments (Blob, GeoRedundant) ---
resource "azurerm_storage_account" "order_attachments" {
name = "orderattachments"
resource_group_name = var.resource_group_name
location = var.location
account_tier = "Standard"
account_replication_type = "GRS"
blob_properties {
versioning_enabled = true
}
min_tls_version = "TLS1_2"
tags = var.tags
}
resource "azurerm_storage_container" "order_attachments_container" {
name = "attachments"
storage_account_name = azurerm_storage_account.order_attachments.name
container_access_type = "private"
}
# --- DNS Records ---
resource "azurerm_dns_a_record" "orders" {
name = "orders"
zone_name = var.dns_zone_name
resource_group_name = var.resource_group_name
ttl = 300
records = [var.load_balancer_ip]
}
resource "azurerm_dns_cname_record" "api_orders" {
name = "api.orders"
zone_name = var.dns_zone_name
resource_group_name = var.resource_group_name
ttl = 300
record = "orders.example.com"
}
resource "azurerm_dns_txt_record" "orders_spf" {
name = "orders"
zone_name = var.dns_zone_name
resource_group_name = var.resource_group_name
ttl = 3600
record {
value = "v=spf1 include:_spf.example.com ~all"
}
}
resource "azurerm_dns_caa_record" "orders_caa" {
name = "orders"
zone_name = var.dns_zone_name
resource_group_name = var.resource_group_name
ttl = 3600
record {
flags = 0
tag = "issue"
value = "letsencrypt.org"
}
}
# --- CDN ---
resource "azurerm_cdn_profile" "order_cdn" {
name = "order-cdn"
resource_group_name = var.resource_group_name
location = "global"
sku = "Standard_Microsoft"
tags = var.tags
}
resource "azurerm_cdn_endpoint" "order_cdn_endpoint" {
name = "order-cdn-endpoint"
profile_name = azurerm_cdn_profile.order_cdn.name
resource_group_name = var.resource_group_name
location = "global"
origin {
name = "order-origin"
host_name = "orders.example.com"
}
is_compression_enabled = true
content_types_to_compress = [
"text/html", "text/css", "application/javascript",
"application/json", "image/svg+xml"
]
querystring_caching_behaviour = "UseQueryString"
tags = var.tags
}
# --- Variables ---
variable "resource_group_name" { type = string }
variable "location" { type = string }
variable "dns_zone_name" { type = string }
variable "load_balancer_ip" { type = string }
variable "tags" {
type = map(string)
default = {}
}One Terraform file. Generated from the same attributes that produce the docker-compose file. The StorageKind.Database generates a PostgreSQL Flexible Server. The StorageKind.Blob generates a Storage Account. DNS records map directly. CDN maps directly. No manual Terraform authoring.
k8s-deployment.yaml
# <auto-generated from [ContainerSpec("order-api")] attributes>
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-api
labels:
app: order-api
spec:
replicas: 3
selector:
matchLabels:
app: order-api
template:
metadata:
labels:
app: order-api
spec:
containers:
- name: order-api
image: order-service:latest
ports:
- containerPort: 80
- containerPort: 443
resources:
limits:
cpu: "1.0"
memory: "1Gi"
requests:
cpu: "0.25"
memory: "256Mi"
livenessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 40
periodSeconds: 30
timeoutSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /health/ready
port: 80
initialDelaySeconds: 10
periodSeconds: 5
securityContext:
runAsNonRoot: true
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false# <auto-generated from [ContainerSpec("order-api")] attributes>
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-api
labels:
app: order-api
spec:
replicas: 3
selector:
matchLabels:
app: order-api
template:
metadata:
labels:
app: order-api
spec:
containers:
- name: order-api
image: order-service:latest
ports:
- containerPort: 80
- containerPort: 443
resources:
limits:
cpu: "1.0"
memory: "1Gi"
requests:
cpu: "0.25"
memory: "256Mi"
livenessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 40
periodSeconds: 30
timeoutSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /health/ready
port: 80
initialDelaySeconds: 10
periodSeconds: 5
securityContext:
runAsNonRoot: true
readOnlyRootFilesystem: true
allowPrivilegeEscalation: falsecert-manager-certificate.yaml
# <auto-generated from [CertificateSpec("orders.example.com")] attributes>
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: orders-example-com-tls
namespace: order-service
spec:
secretName: orders-example-com-tls-secret
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- orders.example.com
- api.orders.example.com
privateKey:
algorithm: ECDSA
size: 256
renewBefore: 720h # 30 days# <auto-generated from [CertificateSpec("orders.example.com")] attributes>
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: orders-example-com-tls
namespace: order-service
spec:
secretName: orders-example-com-tls-secret
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- orders.example.com
- api.orders.example.com
privateKey:
algorithm: ECDSA
size: 256
renewBefore: 720h # 30 daysThe certificate never expires silently. cert-manager handles renewal automatically. The renewBefore: 720h comes from RenewBeforeDays = 30 in the attribute. No calendar reminders. No 3 AM pages.
k8s-ingress.yaml
# <auto-generated from [DnsRecord] + [CertificateSpec] attributes>
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: order-api-ingress
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
tls:
- hosts:
- orders.example.com
- api.orders.example.com
secretName: orders-example-com-tls-secret
rules:
- host: orders.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: order-api
port:
number: 80
- host: api.orders.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: order-api
port:
number: 80# <auto-generated from [DnsRecord] + [CertificateSpec] attributes>
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: order-api-ingress
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
tls:
- hosts:
- orders.example.com
- api.orders.example.com
secretName: orders-example-com-tls-secret
rules:
- host: orders.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: order-api
port:
number: 80
- host: api.orders.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: order-api
port:
number: 80The ingress is generated by combining DNS records and certificate specs. The TLS secret name matches the cert-manager certificate. The hosts match the DNS records. Everything is cross-referenced automatically.
INF001: Container Without Resource Limits
error INF001: [ContainerSpec("order-worker")] does not specify CpuLimit or
MemoryLimit. Containers without resource limits can consume unbounded
resources and cause node eviction in Kubernetes.error INF001: [ContainerSpec("order-worker")] does not specify CpuLimit or
MemoryLimit. Containers without resource limits can consume unbounded
resources and cause node eviction in Kubernetes.This is a hard error, not a warning. A container without resource limits is a production incident waiting to happen. The analyzer enforces this at compile time, before the container is ever built.
INF002: Certificate Without Renewal
error INF002: [CertificateSpec("orders.example.com")] has Provider =
Custom but RenewBeforeDays = 0. Custom certificates require a renewal
policy or they will expire silently.error INF002: [CertificateSpec("orders.example.com")] has Provider =
Custom but RenewBeforeDays = 0. Custom certificates require a renewal
policy or they will expire silently.LetsEncrypt and ACM handle renewal automatically, so RenewBeforeDays is a safety margin. Custom certificates need explicit renewal logic. The analyzer ensures this is not forgotten.
INF003: Storage Without Encryption
error INF003: [StorageSpec("order-logs", StorageKind.Blob)] has Encryption
= false. All storage must be encrypted at rest. Set Encryption = true or
suppress with #pragma if this is intentional.error INF003: [StorageSpec("order-logs", StorageKind.Blob)] has Encryption
= false. All storage must be encrypted at rest. Set Encryption = true or
suppress with #pragma if this is intentional.Encryption at rest is a default. Turning it off requires a conscious decision (pragma suppression with a comment). The analyzer prevents accidental exposure of data.
INF004: DNS Record Without TTL
warning INF004: [DnsRecord("orders.example.com", A, ...)] uses the default
TTL of 300 seconds. Consider whether this is appropriate for production.
Long TTLs reduce DNS query load; short TTLs enable faster failover.warning INF004: [DnsRecord("orders.example.com", A, ...)] uses the default
TTL of 300 seconds. Consider whether this is appropriate for production.
Long TTLs reduce DNS query load; short TTLs enable faster failover.This is a warning, not an error. The default TTL of 300 seconds is reasonable. But the analyzer nudges the team to make a conscious choice rather than accepting defaults silently.
Infrastructure to Cost (Ops.Cost)
Every [ContainerSpec] and [StorageSpec] implies a cost. The Cost DSL (Part 11) reads infrastructure attributes to calculate budgets:
// Infrastructure DSL declares the resource
[StorageSpec("order-db-storage", StorageKind.Database,
Capacity = "100Gi", Tier = "Premium", Iops = 3000)]
// Cost DSL auto-generates a budget line item:
// Database: Premium 100Gi @ 3000 IOPS = ~$X/month
// The cost analyzer warns if the budget is exceeded.// Infrastructure DSL declares the resource
[StorageSpec("order-db-storage", StorageKind.Database,
Capacity = "100Gi", Tier = "Premium", Iops = 3000)]
// Cost DSL auto-generates a budget line item:
// Database: Premium 100Gi @ 3000 IOPS = ~$X/month
// The cost analyzer warns if the budget is exceeded.No separate cost spreadsheet. The infrastructure attributes are the input to cost estimation.
Infrastructure to Networking (Ops.Networking)
Every exposed port in a [ContainerSpec] needs a firewall rule or network policy. The Networking DSL (Part 16) cross-references:
// Infrastructure says: port 5432 is exposed
[ContainerSpec("order-db", "postgres", Ports = new[] { "5432:5432" })]
// Networking DSL must have a firewall rule for 5432.
// If not, cross-analyzer INF-NET001 fires:
// "Container 'order-db' exposes port 5432 but no [FirewallRule] or
// [NetworkPolicy] restricts access to this port."// Infrastructure says: port 5432 is exposed
[ContainerSpec("order-db", "postgres", Ports = new[] { "5432:5432" })]
// Networking DSL must have a firewall rule for 5432.
// If not, cross-analyzer INF-NET001 fires:
// "Container 'order-db' exposes port 5432 but no [FirewallRule] or
// [NetworkPolicy] restricts access to this port."Infrastructure to Observability (Ops.Observability)
Every container needs a health check endpoint. The Observability DSL (Part 7) cross-references:
// Infrastructure says: health check is /health
[ContainerSpec("order-api", "order-service",
HealthCheck = "curl -f http://localhost:80/health || exit 1")]
// Observability DSL should have a matching health probe:
[HealthProbe(typeof(OrderService), "/health", Interval = "30s")]// Infrastructure says: health check is /health
[ContainerSpec("order-api", "order-service",
HealthCheck = "curl -f http://localhost:80/health || exit 1")]
// Observability DSL should have a matching health probe:
[HealthProbe(typeof(OrderService), "/health", Interval = "30s")]Infrastructure to Deployment (Ops.Deployment)
Every [ContainerSpec] is a deployable unit. The Deployment DSL (Part 5) references containers as deployment targets:
// Infrastructure defines the container
[ContainerSpec("order-api", "order-service")]
// Deployment DSL orchestrates it
[DeploymentApp("order-api",
Strategy = DeployStrategy.RollingUpdate,
DependsOn = new[] { "order-db" })]// Infrastructure defines the container
[ContainerSpec("order-api", "order-service")]
// Deployment DSL orchestrates it
[DeploymentApp("order-api",
Strategy = DeployStrategy.RollingUpdate,
DependsOn = new[] { "order-db" })]The Deployment DSL generator validates that every [DeploymentApp] name matches a [ContainerSpec] name. Typos are caught at compile time. Deployment ordering references real containers, not strings in a wiki.
The Infrastructure Gap
Most teams have a gap between "the code" and "the infrastructure the code runs on." The code lives in one repository with CI, code review, and type checking. The infrastructure lives in another repository (or a web console) with different review processes, different ownership, and no type checking.
The Infrastructure DSL closes this gap by putting infrastructure declarations next to the code that depends on them. When a developer adds a new service that needs PostgreSQL, they add a [StorageSpec] attribute in the same pull request. The Terraform is generated. The docker-compose is generated. The Kubernetes manifests are generated. One PR, one review, one source of truth.
The certificate that expired at 3 AM? It is now a [CertificateSpec] attribute with RenewBeforeDays = 30 that generates a cert-manager resource with automatic renewal. The DNS record created in a web console? It is now a [DnsRecord] attribute that generates Terraform. The Dockerfile with hardcoded memory limits? It is now a [ContainerSpec] with explicit MemoryLimit enforced by analyzer INF001.
Infrastructure as code is not new. Infrastructure as typed, compiler-validated, cross-referenced code is.