<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Adnan H. Çelik</title>
    <description>The latest articles on DEV Community by Adnan H. Çelik (@adnan_hamdi).</description>
    <link>https://dev.to/adnan_hamdi</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4166652%2Ffb58827c-1805-408b-b1af-d7a8c64039d9.png</url>
      <title>DEV Community: Adnan H. Çelik</title>
      <link>https://dev.to/adnan_hamdi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/adnan_hamdi"/>
    <language>en</language>
    <item>
      <title>Terraform to Architecture Diagram Without a Plugin</title>
      <dc:creator>Adnan H. Çelik</dc:creator>
      <pubDate>Fri, 16 Oct 2026 21:00:54 +0000</pubDate>
      <link>https://dev.to/adnan_hamdi/terraform-to-architecture-diagram-without-a-plugin-4e7b</link>
      <guid>https://dev.to/adnan_hamdi/terraform-to-architecture-diagram-without-a-plugin-4e7b</guid>
      <description>&lt;p&gt;Every infrastructure repo I have joined had the same two artefacts side by side: a folder of &lt;code&gt;.tf&lt;/code&gt; files that described the system exactly, and a diagram in the wiki that described it roughly, as of some quarter in the past.&lt;/p&gt;

&lt;p&gt;Nobody wants it that way. The problem is that turning Terraform into a picture has always come with a toll:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;terraform graph&lt;/code&gt; needs an initialised working directory, downloads providers, and produces a DOT dependency graph that is accurate and nearly unreadable once you pass a dozen resources.&lt;/li&gt;
&lt;li&gt;Most "Terraform visualiser" tools want a plan file, a state file, or cloud credentials, and some want a plugin in your pipeline before they show you anything.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All I usually want is to look at the code I am reviewing and see its shape. So this post is about doing exactly that, with nothing installed: paste HCL, get a diagram you can edit.&lt;/p&gt;

&lt;p&gt;I am using &lt;a href="https://letdraw.com" rel="noopener noreferrer"&gt;LetDraw&lt;/a&gt; for this. It is a whiteboard with a &lt;strong&gt;Generate from Code&lt;/strong&gt; dialog that reads Terraform (and OpenTofu, which shares the syntax) as text.&lt;/p&gt;

&lt;h2&gt;
  
  
  The example
&lt;/h2&gt;

&lt;p&gt;A small but realistic AWS stack: a VPC with a public and a private subnet, an ALB in front of an ECS service, a Postgres RDS instance, an S3 bucket, a monitoring module, a variable and an output.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"env"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"prod"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_vpc"&lt;/span&gt; &lt;span class="s2"&gt;"main"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_block&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.0.0.0/16"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_subnet"&lt;/span&gt; &lt;span class="s2"&gt;"public"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_block&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.0.1.0/24"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_subnet"&lt;/span&gt; &lt;span class="s2"&gt;"private"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_block&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.0.2.0/24"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_security_group"&lt;/span&gt; &lt;span class="s2"&gt;"web"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"web-sg"&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lb"&lt;/span&gt; &lt;span class="s2"&gt;"app"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"app-${var.env}"&lt;/span&gt;
  &lt;span class="nx"&gt;subnets&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;security_groups&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lb_target_group"&lt;/span&gt; &lt;span class="s2"&gt;"app"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"app-tg"&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_ecs_cluster"&lt;/span&gt; &lt;span class="s2"&gt;"main"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"checkout"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_ecs_service"&lt;/span&gt; &lt;span class="s2"&gt;"api"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"api"&lt;/span&gt;
  &lt;span class="nx"&gt;cluster&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_ecs_cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;subnets&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;load_balancer_target_group_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_lb_target_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_db_instance"&lt;/span&gt; &lt;span class="s2"&gt;"orders"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;identifier&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"orders"&lt;/span&gt;
  &lt;span class="nx"&gt;engine&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"postgres"&lt;/span&gt;
  &lt;span class="nx"&gt;instance_class&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"db.t4g.medium"&lt;/span&gt;
  &lt;span class="nx"&gt;db_subnet_group_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket"&lt;/span&gt; &lt;span class="s2"&gt;"assets"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"checkout-assets"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"monitoring"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./modules/monitoring"&lt;/span&gt;
  &lt;span class="nx"&gt;cluster&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_ecs_cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"lb_dns"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_lb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dns_name&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste it into the dialog (or use &lt;strong&gt;Load file&lt;/strong&gt; on a &lt;code&gt;.tf&lt;/code&gt;), switch on &lt;strong&gt;Add icons&lt;/strong&gt;, and insert:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqa1ncmy2ci8tm7pufvzp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqa1ncmy2ci8tm7pufvzp.png" alt="The Terraform example as an editable diagram with AWS icons" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That is 13 blocks and 13 relationships, grouped into Variables, AWS, Modules and Outputs, with a product icon on each AWS resource. Every box and arrow is a normal shape on a whiteboard, so the next step is the part a generator can never do for you: drag things into the layout your team actually talks about, add the third-party payment API that Terraform knows nothing about, and circle the part you are worried about.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it actually reads
&lt;/h2&gt;

&lt;p&gt;It helps to know what the parser does, so you know what to expect from your own code. It is a static reading of the HCL text:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Blocks become nodes.&lt;/strong&gt; &lt;code&gt;resource&lt;/code&gt;, &lt;code&gt;data&lt;/code&gt;, &lt;code&gt;module&lt;/code&gt;, &lt;code&gt;variable&lt;/code&gt; and &lt;code&gt;output&lt;/code&gt; blocks each become a box. &lt;code&gt;provider&lt;/code&gt;, &lt;code&gt;locals&lt;/code&gt; and the &lt;code&gt;terraform {}&lt;/code&gt; block are configuration, so they are skipped to keep the picture clean.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;References become arrows.&lt;/strong&gt; Any interpolation that points at another declared block draws an arrow from the dependency to the dependent: &lt;code&gt;aws_subnet.public.id&lt;/code&gt;, &lt;code&gt;data.aws_ami.ubuntu.id&lt;/code&gt;, &lt;code&gt;module.network.vpc_id&lt;/code&gt;, &lt;code&gt;var.env&lt;/code&gt;, and the entries of a &lt;code&gt;depends_on&lt;/code&gt; list.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Arrows are labelled with the attribute that created them.&lt;/strong&gt; &lt;code&gt;subnet_ids = [aws_subnet.private.id]&lt;/code&gt; becomes an arrow labelled "subnet"; &lt;code&gt;security_groups = [...]&lt;/code&gt; becomes "security groups". The suffixes &lt;code&gt;_id&lt;/code&gt;, &lt;code&gt;_ids&lt;/code&gt;, &lt;code&gt;_name&lt;/code&gt; and &lt;code&gt;_arn&lt;/code&gt; are dropped, so labels read like English.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cards show what matters.&lt;/strong&gt; The title is a friendly type name (&lt;code&gt;aws_db_instance&lt;/code&gt; reads "DB Instance", &lt;code&gt;azurerm_virtual_network&lt;/code&gt; reads "Virtual Network"). Under it you get the literal &lt;code&gt;name&lt;/code&gt; if there is one, plus up to two key attributes such as &lt;code&gt;cidr_block&lt;/code&gt;, &lt;code&gt;engine&lt;/code&gt;, &lt;code&gt;instance_class&lt;/code&gt;, &lt;code&gt;location&lt;/code&gt; or &lt;code&gt;sku&lt;/code&gt;. Interpolated values are left out rather than shown half-resolved.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Groups follow the provider.&lt;/strong&gt; AWS, Azure, Google Cloud, Kubernetes, Cloudflare, GitHub and other providers each get their own group, so a multi-cloud repo separates itself visually.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Arrows only connect blocks that exist in what you pasted. If &lt;code&gt;module.network&lt;/code&gt; is not in the text, a reference to it is simply ignored instead of inventing a phantom box.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make it readable: hide the noisy relationships
&lt;/h2&gt;

&lt;p&gt;The honest first result of any infrastructure graph is too many arrows. In almost every AWS stack, everything points at the VPC, and those arrows add little.&lt;/p&gt;

&lt;p&gt;The dialog lists the relationship types it found (here: vpc, subnets, security groups, cluster, db subnet group, and so on) and lets you switch them off. The layout still uses every edge, so boxes stay where they are while you toggle. With &lt;code&gt;vpc&lt;/code&gt; hidden:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu9mtvzp0uzf5zq0fnt3y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu9mtvzp0uzf5zq0fnt3y.png" alt="The same diagram with the vpc relationships hidden" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The request path from the load balancer to the service and the database is now the thing you see first. You can also switch between a top-down and a left-to-right layout.&lt;/p&gt;

&lt;h2&gt;
  
  
  The feature I use most: drift against the last import
&lt;/h2&gt;

&lt;p&gt;Diagrams are most useful during review, when you want to see what a change does to the architecture, not what the architecture is.&lt;/p&gt;

&lt;p&gt;Each time you insert a diagram, the dialog remembers it in your browser as the baseline for that format. Paste the new version of the code, turn on &lt;strong&gt;Drift vs last&lt;/strong&gt;, and the preview colours the difference: added blocks and relationships in green, removed ones in red with a dashed outline.&lt;/p&gt;

&lt;p&gt;In the next version of our example, the S3 bucket is gone and an ElastiCache Redis cluster and a second ECS service for background workers have arrived:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl4oeho8h8gn6gmyak20h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl4oeho8h8gn6gmyak20h.png" alt="Drift view: the new worker service and Redis cluster in green, the removed S3 bucket dashed in red" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For a PR, this is the picture I actually want in the description. It answers "what does this change touch?" faster than scrolling a &lt;code&gt;terraform plan&lt;/code&gt;, and it works before anyone has run a plan at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "without a plugin" matters
&lt;/h2&gt;

&lt;p&gt;This approach has a few properties that are easy to overlook until you need them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No credentials, no state.&lt;/strong&gt; The parser reads text. It never talks to AWS, never needs a backend, and does not care whether the code has been applied.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No &lt;code&gt;terraform init&lt;/code&gt;.&lt;/strong&gt; No providers are downloaded and no working directory is needed, so it works on a branch you just checked out, on a snippet from a code review, or on a module you are only evaluating.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It runs in your browser.&lt;/strong&gt; The parsing and layout happen client-side, so your resource names and CIDR ranges are not sent to a server to build the picture.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Licensing-neutral.&lt;/strong&gt; Nothing is run or redistributed; the HCL is only parsed as text, so the same flow works for Terraform and OpenTofu code alike.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Limits worth knowing
&lt;/h2&gt;

&lt;p&gt;A static parser is honest about what it cannot know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;count&lt;/code&gt; and &lt;code&gt;for_each&lt;/code&gt; are not expanded. A resource with &lt;code&gt;count = 3&lt;/code&gt; is one box, which is usually what you want in an architecture view anyway.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;module&lt;/code&gt; is one box. Its internals are not followed; if you want them, paste the module's own files and import them as a separate diagram.&lt;/li&gt;
&lt;li&gt;Values that only exist after apply (IDs, ARNs, DNS names) are not resolved.&lt;/li&gt;
&lt;li&gt;The local name of a resource (the &lt;code&gt;app&lt;/code&gt; in &lt;code&gt;aws_lb.app&lt;/code&gt;) is not shown on the card; the card uses the type, the &lt;code&gt;name&lt;/code&gt; attribute and key attributes instead. Variables, modules and outputs therefore show their kind rather than their name.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Load file&lt;/strong&gt; takes one file at a time. For a whole folder, concatenate first (&lt;code&gt;cat *.tf&lt;/code&gt;) and paste the result.&lt;/li&gt;
&lt;li&gt;Generate from Code is part of the Pro plan.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Automating it
&lt;/h2&gt;

&lt;p&gt;The same converter is available outside the editor. With a personal API token, &lt;code&gt;POST https://api.letdraw.com/api-v1/documents/from-code&lt;/code&gt; with the Terraform text in the body creates the diagram in a workspace, and the &lt;code&gt;diagram_from_code&lt;/code&gt; tool does the same for AI coding assistants over MCP. That makes it possible to regenerate the diagram from CI whenever the infrastructure folder changes. (Icons and the drift view are editor features; the API returns the plain diagram.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;a href="https://letdraw.com" rel="noopener noreferrer"&gt;letdraw.com&lt;/a&gt; and choose &lt;strong&gt;Generate from Code&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Paste a &lt;code&gt;.tf&lt;/code&gt; file (or a few concatenated) and look at the live preview.&lt;/li&gt;
&lt;li&gt;Turn on &lt;strong&gt;Add icons&lt;/strong&gt;, hide the relationships that add noise, and insert.&lt;/li&gt;
&lt;li&gt;Next time the code changes, paste the new version and turn on &lt;strong&gt;Drift vs last&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The diagram you can generate in thirty seconds from the code under review will always beat the beautiful one in the wiki that nobody trusts. Give it a try on your own repo, and tell me which provider or block type it should understand better.&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>devops</category>
      <category>aws</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Stop Redrawing Your Architecture: YAML, SQL, Terraform and EXPLAIN Plans to Diagrams in One Paste</title>
      <dc:creator>Adnan H. Çelik</dc:creator>
      <pubDate>Tue, 06 Oct 2026 14:46:04 +0000</pubDate>
      <link>https://dev.to/adnan_hamdi/stop-redrawing-your-architecture-yaml-sql-terraform-and-explain-plans-to-diagrams-in-one-paste-39lc</link>
      <guid>https://dev.to/adnan_hamdi/stop-redrawing-your-architecture-yaml-sql-terraform-and-explain-plans-to-diagrams-in-one-paste-39lc</guid>
      <description>&lt;p&gt;I have a confession: for years, the most out-of-date file in every repo I worked on was docs/architecture.png.&lt;/p&gt;

&lt;p&gt;It was never anyone's fault. The diagram was correct on the day someone drew it. Then a service got split, a Redis cache appeared, a queue was added, a table gained a foreign key. Nobody wanted to open a drawing tool and push boxes around again, so the picture slowly turned into fiction, and new teammates learned the system by reading YAML instead.&lt;/p&gt;

&lt;p&gt;The thing is, the YAML (and the SQL, and the HCL) already describes the system. The picture is just a different view of the same source of truth. That is the idea behind LetDraw: a hand-drawn whiteboard that can read the files engineers already have and draw them for you, as real, editable shapes.&lt;/p&gt;

&lt;p&gt;This post is a practical tour. No marketing slides, just six things I actually use it for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kubernetes YAML to diagram&lt;/li&gt;
&lt;li&gt;Docker Compose to diagram&lt;/li&gt;
&lt;li&gt;SQL to ER diagram&lt;/li&gt;
&lt;li&gt;Terraform diagram, online&lt;/li&gt;
&lt;li&gt;Postgres EXPLAIN visualizer&lt;/li&gt;
&lt;li&gt;Hand-drawn ER diagrams&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The one feature everything else builds on: Generate from Code&lt;br&gt;
All of the conversions below go through a single dialog called Generate from Code. You paste text (or load a file) and it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;auto-detects the format, so you never pick "Kubernetes" or "SQL" from a dropdown,&lt;/li&gt;
&lt;li&gt;shows a live preview before anything touches your canvas,&lt;/li&gt;
&lt;li&gt;inserts normal shapes, not an image. Every box, label and arrow is editable afterwards.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point is the whole difference between a diagram generator and a diagram renderer. A rendered image is read-only; you either accept the layout or you go back and fight the source. Here, auto-layout gets you most of the way, and then you nudge a box, add a sticky note, or highlight the risky path like you would on any whiteboard.&lt;/p&gt;

&lt;p&gt;A few other options show up across all formats:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add icons stamps recognisable product icons (Postgres, Redis, Kubernetes, AWS, Azure, GCP and more) onto the boxes.&lt;/li&gt;
&lt;li&gt;Drift vs last compares the current import with your previous one: added resources in green, removed ones in red. Great for change reviews.&lt;/li&gt;
&lt;li&gt;Layout direction (top-down or left-right) and which relationship types to show.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Honest note up front: drawing by hand, templates, shape libraries and PNG/SVG/PDF export are free. Generate from Code is part of the Pro plan ($5/month). I will point out where that matters.&lt;/p&gt;
&lt;h2&gt;
  
  
  1. Kubernetes YAML to diagram
&lt;/h2&gt;

&lt;p&gt;Here is a trimmed version of a typical service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;shop&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;shop&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;shop/web:1.4&lt;/span&gt;
          &lt;span class="na"&gt;envFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;configMapRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web-config&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste that (multi-document YAML is fine, or load the whole file) and you get a cluster diagram where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;resources are grouped by namespace, so the layout mirrors how you think about the cluster instead of one flat soup of nodes,&lt;/li&gt;
&lt;li&gt;Services are linked to the workloads they select, based on the actual label selectors,&lt;/li&gt;
&lt;li&gt;workloads are linked to the ConfigMaps, Secrets and PersistentVolumeClaims they mount.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It understands Deployments, StatefulSets, Services, Ingress, ConfigMaps, Secrets, volume claims and RBAC objects, so a permissions review can start from a picture of which service accounts are bound to which roles.&lt;/p&gt;

&lt;p&gt;Helm users: paste the output of helm template to see the rendered resources, or paste a Chart.yaml to see the chart and its subchart dependencies.&lt;/p&gt;

&lt;p&gt;Where this has saved me the most time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manifest reviews. Import the manifests from a PR, then turn on Drift vs last to see exactly which resources were added or removed.&lt;/li&gt;
&lt;li&gt;Onboarding. New engineers get the platform as one picture before they open forty YAML files.&lt;/li&gt;
&lt;li&gt;Incidents. The cluster diagram sits next to the runbook, with comments pinned on the parts that broke.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More detail: &lt;a href="https://www.letdraw.com/kubernetes-diagram-tool" rel="noopener noreferrer"&gt;Kubernetes diagram tool&lt;/a&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  2. Docker Compose to diagram
&lt;/h2&gt;

&lt;p&gt;Compose files are even simpler, which makes them a perfect first try:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app:latest&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:16&lt;/span&gt;
  &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis:7&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste it and four services appear, laid out and connected. With Add icons on, the Postgres service becomes a database cylinder, Redis gets its own icon, and the depends_on edges become arrows that route around boxes instead of through them (LetDraw has an obstacle-avoiding "smart" connector, which is a small detail until you have tried to read a diagram where every arrow crosses three boxes).&lt;/p&gt;

&lt;p&gt;Same dialog, same auto-detection as Kubernetes, so you do not need to tell it which one you pasted.&lt;/p&gt;

&lt;p&gt;The workflow I recommend: keep the generated diagram next to the compose file, and when the stack changes, re-import and check Drift vs last rather than editing the drawing by hand. The picture stays right by construction, not by discipline.&lt;/p&gt;

&lt;p&gt;There is a longer walkthrough on the blog: &lt;a href="https://www.letdraw.com/blog/compose-to-diagram" rel="noopener noreferrer"&gt;Docker Compose diagram, from docker-compose.yml to clean architecture.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. SQL to ER diagram
&lt;/h2&gt;

&lt;p&gt;This is the one my backend friends ask about most. Take your migrations or a schema-only dump:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt;    &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt;  &lt;span class="nb"&gt;text&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt;         &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;user_id&lt;/span&gt;    &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;total&lt;/span&gt;      &lt;span class="nb"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="n"&gt;timestamptz&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;order_items&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;order_id&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;qty&lt;/span&gt;      &lt;span class="nb"&gt;int&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste it and you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;every table as a grid with a header and one row per column, type next to name,&lt;/li&gt;
&lt;li&gt;PK and FK markers, with long types shortened (timestamptz, varchar) so tables stay compact,&lt;/li&gt;
&lt;li&gt;relationships drawn between tables, and you choose the style: plain line, arrow, smart routed connector, or crow's foot notation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The crow's foot cardinality is not a guess. It is inferred from the foreign key column itself: a NOT NULL FK means "exactly one" on the parent side, a nullable one means "zero or one", and a UNIQUE FK turns one-to-many into one-to-one.&lt;/p&gt;

&lt;p&gt;It handles Postgres, MySQL and standard SQL, including quoted names, schema prefixes (public.users), IF NOT EXISTS, inline REFERENCES and separate FOREIGN KEY constraints. Comments are ignored, so you can paste a migration file as-is.&lt;/p&gt;

&lt;p&gt;Two details I like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;While the generated ERD is unchanged, Diagram to Code gives it back as Mermaid erDiagram code. Handy if your docs live in Markdown.&lt;/li&gt;
&lt;li&gt;Everything is normal shapes, so you can sketch the proposed tables for a new feature right next to the current schema and argue about it with comments on the relationships.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More: &lt;a href="https://www.letdraw.com/sql-to-er-diagram" rel="noopener noreferrer"&gt;SQL to ER diagram&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Terraform diagram, online
&lt;/h2&gt;

&lt;p&gt;No CLI to install, no graph tooling, no cloud credentials. Paste HCL into the browser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_vpc"&lt;/span&gt; &lt;span class="s2"&gt;"main"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_block&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.0.0.0/16"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_subnet"&lt;/span&gt; &lt;span class="s2"&gt;"app"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_block&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.0.1.0/24"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_security_group"&lt;/span&gt; &lt;span class="s2"&gt;"web"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_instance"&lt;/span&gt; &lt;span class="s2"&gt;"web"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;instance_type&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"t3.micro"&lt;/span&gt;
  &lt;span class="nx"&gt;subnet_id&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_security_group_ids&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;LetDraw reads resource, data, module, variable and output blocks and turns references into arrows: aws_vpc.main.id, var.region, depends_on. Each arrow is labelled with the attribute that created the link (vpc_id, subnet_id), which is exactly the information you want during a review.&lt;/p&gt;

&lt;p&gt;Each resource becomes a card with its type, its name and a couple of key settings (CIDR block, instance type). Resources are grouped by provider, so a multi-cloud estate keeps AWS, Azure and GCP in separate frames, and modules, variables and outputs get their own frames too.&lt;/p&gt;

&lt;p&gt;Use cases where it pays off:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Architecture reviews: show reviewers what the code actually builds instead of walking them through file after file.&lt;/li&gt;
&lt;li&gt;Change reviews: paste the new version, turn on Drift vs last, and see added and removed resources and links.&lt;/li&gt;
&lt;li&gt;Dependency checks: before renaming a variable or a module output, see who reads it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More: &lt;a href="https://www.letdraw.com/terraform-diagram" rel="noopener noreferrer"&gt;Terraform diagram generator&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Postgres EXPLAIN visualizer
&lt;/h2&gt;

&lt;p&gt;This one is not an architecture diagram at all, and it is the feature that surprised people the most when I showed it around.&lt;/p&gt;

&lt;p&gt;Run your query with a JSON plan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;EXPLAIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ANALYZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FORMAT&lt;/span&gt; &lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="s1"&gt;'7 days'&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste the JSON output and you get a tree, one box per plan node, showing the node type, the table, index or function it touches, cost, rows and, with ANALYZE, the time in milliseconds across all loops.&lt;/p&gt;

&lt;p&gt;The important part is the heat map. Postgres reports cost and time cumulatively, which is why the root node of every plan looks like the most expensive one. The visualizer subtracts each node's children and colours by self time, from green through amber to red. So the node that actually hurts (that sequential scan on orders, say) lights up red, instead of the Sort at the top that merely inherits its children's time.&lt;/p&gt;

&lt;p&gt;It also takes MySQL EXPLAIN FORMAT=JSON, showing each table with its access type, cost and rows.&lt;/p&gt;

&lt;p&gt;And it runs in your browser, so the plan, your table names and your query text stay on your machine. That matters when the query touches production schemas you would rather not paste into a random website.&lt;/p&gt;

&lt;p&gt;Things I now do with it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Before and after an index: both plans on one canvas, and you can see which node turned from red to green.&lt;/li&gt;
&lt;li&gt;Code review: attach the plan of a new query to the PR so reviewers see its cost, not just its SQL.&lt;/li&gt;
&lt;li&gt;Post-mortems: export the heat-mapped tree as SVG and put the culprit right in the write-up.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More: &lt;a href="https://www.letdraw.com/explain-plan-visualizer" rel="noopener noreferrer"&gt;EXPLAIN plan visualizer&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Hand-drawn ER diagrams (and why the sketchy look is a feature)
&lt;/h2&gt;

&lt;p&gt;Everything above is generated, but LetDraw is first and foremost a whiteboard, and the default style is hand-drawn.&lt;/p&gt;

&lt;p&gt;I used to think that was purely aesthetic. It is not. A sketchy diagram signals "this is a draft, please argue with it." People comment on a hand-drawn ERD in a design review far more freely than on a polished one that looks final. That is exactly the conversation you want before the first migration is written.&lt;/p&gt;

&lt;p&gt;What makes it usable for real data modelling rather than doodling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Crow's foot cardinality as native arrowheads: one, zero or one, zero or many, one or many. You pick them on the arrow ends, no hand-drawn markers to keep aligned.&lt;/li&gt;
&lt;li&gt;A Database Schema template to start from, plus SQL import when you want the real thing.&lt;/li&gt;
&lt;li&gt;A Sketchiness setting (Clean, Sketch, Scribble), so the same ERD can go from whiteboard draft to clean documentation without redrawing it.&lt;/li&gt;
&lt;li&gt;Mermaid ER import and export, and AI drafts from a plain-language description.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Drawing ER diagrams by hand, with all of the above, is free.&lt;/p&gt;

&lt;p&gt;More: &lt;a href="https://www.letdraw.com/er-diagram-tool" rel="noopener noreferrer"&gt;ER diagram tool&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What sets it apart (from a developer's point of view)
&lt;/h2&gt;

&lt;p&gt;I have used a lot of diagram tools. Most fall into one of two camps: pretty whiteboards that know nothing about your stack, or diagrams-as-code renderers that know your stack but give you a read-only image. LetDraw sits in the middle, and that is the point:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code in, editable shapes out. Kubernetes, Helm, Compose, Terraform, SQL, EXPLAIN plans, Graphviz DOT, PlantUML, OpenAPI, git log, Mermaid and D2 all become real shapes you can move and annotate.&lt;/li&gt;
&lt;li&gt;And back to code. Diagram to Code exports Mermaid or D2, so a diagram can live in your repo and be reviewed like code.&lt;/li&gt;
&lt;li&gt;Local-first and private. No install, works offline, drawings stay in your browser, no third-party tracking. Live collaboration and shared snapshots are end-to-end encrypted in the browser with AES-GCM.&lt;/li&gt;
&lt;li&gt;Real-time collaboration with live cursors, roles, and comments you can pin to any point on the canvas, not just to a shape.&lt;/li&gt;
&lt;li&gt;Embeds that do not go stale. Drop a snippet into a README or wiki for a live, read-only view, instead of a PNG someone must remember to re-export.&lt;/li&gt;
&lt;li&gt;Automation. There is a REST API and an MCP server, so CI jobs and AI agents can create and update diagrams, too.&lt;/li&gt;
&lt;li&gt;AI that is not a paywall trap. Every plan gets a few free AI requests; after that you can bring your own provider key (BYOK) for unlimited use.&lt;/li&gt;
&lt;li&gt;Self-hosting is available on the Enterprise plan, for teams that need diagrams to stay inside their own network.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why DevOps and software teams should care
&lt;/h2&gt;

&lt;p&gt;Diagrams are not documentation for documentation's sake. They are how we:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;onboard people faster than any wiki page,&lt;/li&gt;
&lt;li&gt;review infrastructure and schema changes without reading every file,&lt;/li&gt;
&lt;li&gt;explain incidents to people who were not on the call,&lt;/li&gt;
&lt;li&gt;argue productively about trade-offs before code exists.
The reason teams skip them is cost: drawing is slow, and keeping drawings current is slower. If the diagram can be regenerated from the same YAML, HCL or SQL that runs in production, that cost mostly disappears, and the diagram stops being fiction.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it in two minutes
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open letdraw.com. No sign-up needed to start drawing.&lt;/li&gt;
&lt;li&gt;Grab a docker-compose.yml, a Kubernetes manifest, some CREATE TABLE statements or an EXPLAIN (FORMAT JSON) output from your own project.&lt;/li&gt;
&lt;li&gt;Open Generate from Code, paste, and look at the preview.&lt;/li&gt;
&lt;li&gt;Insert it, move a few boxes, and share the link with a teammate.
If you try it on a real project, I would love to hear what broke, what surprised you, and which format you want next. Drop a comment below.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>architecture</category>
      <category>documentation</category>
      <category>terraform</category>
      <category>tools</category>
    </item>
  </channel>
</rss>
