Skip to main content

On This Page

Infrastructure as Code with TSX to Terraform: 8 Lines Generate 248 Lines of AWS Infrastructure

3 min read
Share

These articles are AI-generated summaries. Please check the original sources for full details.

Infrastructure as Code with OpenTofu/Terraform

Dinghy introduces a two-layer Infrastructure as Code system built on TSX components that render to Terraform JSON. A single composite component called Ec2Servers generates 248 lines of Terraform from just eight lines of TypeScript.

Why This Matters

Infrastructure as Code promises reproducibility, but real-world Terraform projects often devolve into thousands of lines of copy-pasted resource blocks with manual reference wiring and no type safety. Dinghy’s two-layer model—basic 1:1 components for control and composite components for opinionated defaults—reduces boilerplate while preserving the ability to override any piece. The Ec2Servers composite alone eliminates the need to manually write and connect a dozen interdependent resources (VPC, subnet, IAM, security groups) for each server, cutting deployment errors from miswired references at the cost of understanding when to drop down to raw components.

Key Insights

  • Basic components map 1:1 to Terraform resources: aws_instance becomes AwsInstance, with every argument as a typed prop and no added opinions, covering around 245 AWS services.
  • Composite components like Ec2Servers assemble a full server stack (VPC, subnet, internet gateway, route, security group, IAM role, AMI lookup, instance) and create only missing resources based on inspection of props.
  • Context lookups via useAwsAmi(), useAwsSubnet(), and useAwsInstanceProfile() wire dependencies automatically without manual prop passing through the tree.
  • Configuration overrides through dinghy.config.yml let users customize instance type and OS flavor without dropping down to raw resources, enabling mixed-architecture deployments.

Working Examples

Basic component example: creating an EC2 instance with one component per Terraform resource

import { Shape } from '@dinghy/base-components'
import { AwsProvider } from '@dinghy/tf-aws'
import { LocalBackend } from '@dinghy/tf-common'
import { AwsInstance } from '@dinghy/tf-aws/serviceEc2'

export default () => (
  <Shape _title='Server With Basic Building Blocks'>
    <AwsProvider>
      <Server />
      <LocalBackend />
    </AwsProvider>
  </Shape>
)

const Server = (props: any) => (
  <AwsInstance
    ami='ami-005e54dee72cc1d00'
    instance_type='t3.nano'
    _title='my-demo-server'
    {...props}
  />
)

Composite component example: eight lines of TSX generating a full server stack including VPC, subnet, security group, IAM, and AMI lookup

import { AwsStack } from '@dinghy/tf-aws'
import { Ec2Servers } from '@dinghy/tf-aws/ec2'

export default () => (
  <AwsStack>
    <Ec2Servers />
  </AwsStack>
)

Configuration to override composite defaults, producing two servers with different architectures and distributions

# dinghy.config.yml
servers:
  web1:
    instance_type: t4g.nano  # arm64 instead of the t3.nano default
    linuxFlavor: ubuntu
  web2:
    instance_type: t3.small

Practical Applications

  • Start with composite components like Ec2Servers for standard server setups, then drop down to basic components only for resource types not covered by composites.
  • Override behavior via dinghy.config.yml to customize instance size or Linux distribution, avoiding the anti-pattern of forking and modifying the composite component source.
  • Pass explicit props like subnet_id, ami, or iam_instance_profile to bypass composite generation of those resources, preventing unnecessary resource duplication when using shared infrastructure.

References:

Continue reading

Next article

Why Single-Purpose Agents Beat One Big Automation Script: A Homelab Case Study

Related Content