Mo Sharif
← ~/blog

Architecture Diagram to Terraform, K8s, and Compose

Your architecture diagram shouldn't just be a picture.

When you've got a PostgreSQL node connected to an API service on the canvas, that relationship contains real information. Enough information to generate a valid docker-compose.yml with both services, proper networking, health checks, and environment variables. Enough to scaffold Terraform resources or Kubernetes manifests.

Infrastructure export

is a code generator that reads the nodes and edges of an architecture diagram and emits runnable Docker Compose, Terraform, or Kubernetes files for that system.

So I built it. You draw an architecture on Codelit — or generate one, or point it at a GitHub repo and let it read your codebase — and it exports real infrastructure code. Not pseudocode. Not markdown. Real files that docker-compose up, terraform apply, or kubectl apply will actually accept. It's a Pro feature, and I'll be upfront about why.

Every node on the Codelit canvas carries a type, a subtype, and properties, and the export system maps that combination to concrete infrastructure. The mapping is target-specific, so the same PostgreSQL node produces three genuinely different files depending on which format you asked for.

It doesn't matter how the node got there — dragged on by hand, generated from a prompt, or created when the Mermaid parser turned pasted syntax into a live canvas. Once it's a typed node, it's exportable.

Docker Compose:

Yaml
services:
  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: app_db
      POSTGRES_USER: ${DB_USER:-postgres}
      POSTGRES_PASSWORD: ${DB_PASSWORD:-changeme} # Replace in production!
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
    ports:
      - "5432:5432"

Terraform (AWS):

Hcl
resource "aws_db_instance" "postgres" {
  identifier     = "app-postgres"
  engine         = "postgres"
  engine_version = "16.1"
  instance_class = "db.t3.medium"

  allocated_storage     = 20
  max_allocated_storage = 100
  storage_type          = "gp3"

  db_name  = "app_db"
  username = var.db_username
  password = var.db_password

  db_subnet_group_name   = aws_db_subnet_group.main.name
  vpc_security_group_ids = [aws_security_group.postgres.id]

  multi_az            = var.environment == "production" ? true : false
  skip_final_snapshot = var.environment != "production"

  tags = {
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}

Kubernetes:

Yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
spec:
  serviceName: postgres
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    spec:
      containers:
        - name: postgres
          image: postgres:16-alpine
          envFrom:
            - secretRef:
                name: postgres-credentials
          volumeMounts:
            - name: postgres-data
              mountPath: /var/lib/postgresql/data
  volumeClaimTemplates:
    - metadata:
        name: postgres-data
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 20Gi

Each mapping includes sensible defaults, so you don't have to specify every parameter. A dev-flagged export gets smaller instances and simpler networking. Production-flagged exports include replication, backups, and proper security groups.

Edges — the connections between services on a Codelit diagram — are where infrastructure generation actually matters. Nodes are the easy part. An arrow from a Node.js API to PostgreSQL has to become working networking, and "working networking" means something different in every export target.

Edge on the canvasExport targetInfrastructure the export has to generate
Sync call, Node.js API → PostgreSQLDocker ComposeBoth services on the same network, with the database hostname injected into the API's env vars
Sync call, Node.js API → PostgreSQLTerraform (AWS)Security group rules allowing port 5432 from the API's subnet to the database's subnet
Sync call, Node.js API → PostgreSQLKubernetesThe API deployment referencing the database's Service DNS name, postgres.default.svc.cluster.local
Async event, Service A → RabbitMQ → Service BAny targetThe broker resource, exchange and queue declarations, and connection strings in both services' env vars

That last row is the expensive one. A dashed arrow means async, and that solid-versus-dashed distinction on the React Flow canvas is the signal the export reads.

I maintain a connection matrix of about 40 source-target-edge combinations that define what infrastructure each connection type needs. A sync call from service to database needs different rules than an async event through a message broker. The matrix handles all of them.

A React frontend, an Express API, and a PostgreSQL database — three nodes, two edges — export as one docker-compose.yml with build contexts, port mappings, health checks, and dependency conditions wired between all three services.

Yaml
version: "3.8"

services:
  # React frontend - served by nginx in production
  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile
    ports:
      - "3000:80"
    depends_on:
      api:
        condition: service_healthy
    environment:
      - API_URL=http://api:4000

  # Express API server
  api:
    build:
      context: ./api
      dockerfile: Dockerfile
    ports:
      - "4000:4000"
    depends_on:
      postgres:
        condition: service_healthy
    environment:
      - DATABASE_URL=postgresql://postgres:changeme@postgres:5432/app_db
      - NODE_ENV=development
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:4000/health"]
      interval: 10s
      timeout: 5s
      retries: 3

  # PostgreSQL database
  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: app_db
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: changeme # CHANGE THIS
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  postgres_data:

Drop this in a project, run docker-compose up, and services start talking to each other. Health checks ensure proper startup order. Environment variables wire everything together. Comments tell you what to customize.

That last point matters more than you'd think. I've found that comments in generated code dramatically increase the chance someone actually uses and modifies the output instead of tossing it.

Codelit maps six common node families across all three export targets, and each target has its own quirks. Docker Compose is the most direct mapping, nearly 1:1. Terraform generates 5-6 resources per node. Kubernetes is the most verbose of the three.

Canvas NodeDocker ComposeTerraform (AWS)Kubernetes
PostgreSQLpostgres serviceaws_db_instance (RDS)StatefulSet + PVC
Redisredis serviceaws_elasticache_clusterDeployment + Service
API Servicebuild context serviceaws_ecs_task_definitionDeployment + Service
Queue (RabbitMQ)rabbitmq serviceaws_mq_brokerStatefulSet + Service
S3 Storageminio service (local)aws_s3_bucketPVC (or external)
Load Balancernginx serviceaws_lb + aws_lb_listenerIngress

The Terraform expansion is the instance, subnet group, security group, parameter group, and so on. The Kubernetes expansion is worse: a single service on the canvas becomes a Deployment, a Service, an HPA, a ConfigMap, and a Secret. One box, five files' worth of YAML.

Infrastructure export sits behind Pro because the AI cost per export is materially higher than diagram generation, and because the correctness bar is higher too. I'd rather explain that than pretend it's a packaging decision.

Generating valid Terraform for a 20-node architecture requires a big context window prompt: the full node graph, target provider resource schemas, connection matrix rules, and output examples. A single Terraform export for a complex architecture costs 5-10x what the initial diagram generation costs in API tokens.

That math doesn't work on free tier. I tried. My AWS bill disagreed. Codelit began as a Lucidchart rage-quit, and every feature since has had to justify its own token bill.

Beyond cost, there's a quality bar. Generated IaC needs to be correct enough to actually run. Bad Terraform creates real resources that cost real money. Bad Kubernetes manifests cause production incidents. The validation, testing, and provider-specific tuning I've put into this feature is substantial, and it needs to pay for itself.

Generated infrastructure code from Codelit is a starting point, not production config. I'm explicit about this in the UI and in the generated files themselves, because the failure mode of a plausible-looking Terraform file is worse than the failure mode of a broken one.

What you'll need to add yourself:

  • Real secrets. The generated files use placeholders. Wire them to whatever secret manager you already run before anything gets applied.
  • Custom domains and SSL. DNS records, certificate provisioning, domain routing: too environment-specific to generate reliably.
  • Monitoring and alerting. Basic health checks are included. Proper observability (Datadog, Grafana, PagerDuty) is not.
  • Cost optimization. Generated configs use sensible defaults, not cost-optimized instance types. Check the cost estimator for that, and the design-time pricing argument behind it.
  • Compliance. No HIPAA, SOC2, or PCI configurations. That's a whole different conversation.

What it does give you is the boilerplate: networking, service definitions, dependency graphs, basic resource config. For a 15-node architecture, that's easily 2-3 hours of manual work eliminated. And the generated version is often more consistent than hand-written IaC, because it follows the same patterns throughout instead of drifting as whoever wrote it got tired.

Two patterns dominate: Docker Compose exports become same-day local dev environments, and Terraform exports become scaffolding that experienced engineers customize before they run terraform apply. The reaction I hear most, from both groups, is "Wait, this actually works?"

People expect generated code to be garbage. When they run docker-compose up and see services starting and connecting, there's genuine surprise. One user drew his startup's architecture on a Friday afternoon, exported Docker Compose, and had a working local dev environment by that evening. Not production-ready, but a fully functional setup his team could develop against on Monday.

The Terraform users are more experienced and more critical, which is appropriate. They use the generated config as scaffolding: resource structure and networking are useful, but they customize instance types, add monitoring, and tighten security groups before running terraform apply. That's exactly the right way to use it.

The thing worth taking from all of this is smaller than the feature: the drawing you already made is a data structure, and treating it as one is the difference between a diagram that documents a system and a diagram that produces it. Check out the full infrastructure export feature or see pricing for Pro access.

Questions people actually ask

Can you generate Terraform from an architecture diagram?
Yes. Codelit's infrastructure export reads the nodes and edges on an architecture diagram and emits Terraform for AWS, alongside Docker Compose and Kubernetes manifests for the same system. Each node maps to target-specific resources, so a PostgreSQL node becomes an RDS instance with a subnet group, a security group and a parameter group attached. Terraform typically produces five to six resources for a single node on the canvas.
Is generated infrastructure code production ready?
No. Treat it as a starting point. The generated files use placeholder secrets, carry no DNS or SSL configuration, include only basic health checks rather than real observability, use sensible rather than cost-optimized instance types, and contain no compliance profiles for HIPAA, SOC 2 or PCI. What they do give you is the boilerplate: networking, service definitions, dependency graphs and basic resource config. On a fifteen-node architecture that is two to three hours of manual work removed.
How does a diagram know what infrastructure to generate?
Through node types and edge types. Every node on the Codelit canvas carries a type, a subtype and properties, which the export system maps to resources for whichever target you picked. Connections matter just as much. A matrix of roughly forty combinations of source, target and edge type decides whether a given connection becomes a shared Docker network, a security group rule, or a Kubernetes Service DNS reference.
Why is infrastructure export a paid feature?
Because the AI cost per export is materially higher than for diagram generation. Producing valid Terraform for a twenty-node architecture needs a large prompt carrying the full node graph, the target provider's resource schemas, the connection matrix rules and output examples. A single Terraform export for a complex architecture costs five to ten times what the initial diagram generation costs in API tokens, which does not survive a free tier.
What is the difference between the Docker Compose, Terraform and Kubernetes exports?
Volume and directness. Docker Compose is close to a one-to-one mapping, so a service on the canvas becomes a service in the file. Terraform expands each node into five or six AWS resources, including the instance itself plus subnet groups, security groups and parameter groups. Kubernetes is the most verbose of the three, because a single service becomes a Deployment, a Service, an HPA, a ConfigMap and a Secret.
Will docker-compose up actually work on the generated file?
For local development, yes. The generated Compose file includes build contexts, port mappings, health checks and dependency conditions, so services start in the right order and can reach one another. The passwords in it are placeholders and have to be replaced before anything leaves your machine. One user drew his startup's architecture on a Friday afternoon and had a working local dev environment running that same evening.