A guide to creating an AWS cloud service with Terraform My weekend goal was to finally publish something to . I’ve been playing around with and have been looking for an excuse to build an actual project so I decided to pull the trigger and finally commit to it. my domain Terraform While thinking about what to make exactly, I came to the realization that I don’t have a lot of content for a typical portfolio but I share work on other services like Github and . I know I also don’t want to spend a bunch of time thinking about what to put on it. Twitter What I ended up building was a self-updating static website. The web service summarizes my latest activity from , Twitter and Medium, generates a static website, and publishes the content to my domain. Besides serving as an over-engineered, non-transferable business card, it’s a convenient launcher for the apps I use most . Github when added to the iOS home screen Here’s the final product: — Add to iOS home screen jschr.io I built it with React, Webpack and Terraform. If you want to skip right to the code, its available along with instructions on how to deploy your own version. on Github Read on if you’re interested in an in-depth overview of the selected stack and project structure. The examples are written in and assumes some familiarity with and . Typescript React Webpack What’s Terraform? is a tool for creating infrastructure as code with declarative configuration files. One major benefit of writing your infrastructure as code is for free, you get all the power of version control, code reviews and collaboration. Terraform With Terraform’s declarative configuration files you can use and to make it a really easy to create and manage your infrastructure. Terraform is similar to AWS’ CloudFormation, except that it has a you can use to create resources in many other cloud services. variables modules growing list of providers Heres what a Terraform config looks like: Checkout out for more use cases and comparisons. Terraform is built by the team over at . the intro docs Hashicorp Project structure Let’s start with an overview of the app’s structure: Creating the Webpack config The first step and where most of the magic happens is creating the webpack config. This project wouldn’t have been nearly as easy without this awesome for webpack. static website generator plugin Here’s a simple webpack config using the plugin: : Webpack supports Typescript for config files if it has a .ts extension and is installed locally for your project. Tip #1 ts-node Every property passed in through the locals option of the static site generator plugin will be sent to the server-side render function. Using the locals option is how we will pass in the app’s props whenever we generate a new static website. To make our site more , the next step is hooking it up to some real data. You might pull data from a local markdown file or fetch it from an API. Before being able to fetch anything, we’ll need to make a couple changes to the webpack config. dynamic In order to make use of we are going convert our config to export an async function, which works out-of-the box with webpack. We can then fetch our latest activity from Github and render the app with the getProps function. async/await : Webpack supports exporting a function from the config. It will receive any env options you . Tip #2 set via the command line The full source of the webpack config can be . The same config is used to start the dev server and to generate a new static website in our Lambda function. found in the repo Server-side rendering The server-side render function receives the app props we fetched in the webpack config as well as a we can use to inject the javascript bundle into the html. stats object The template receives the initial app render as an html string, any data that the client needs from the server (accessible via the ) and any javascript bundles that need to be injected. window object Mounting to the DOM After rendering the initial page load, the browser needs to bootstrap the React app with the same props that were used to render the html. This happens in the mount function and we need to make sure that it’s . only called when running in the browser Note that index.ts runs in both and so when importing libraries that depend on the browser APIs. node browser contexts you need to be extra cautious Now that we’ve setup server-side rendering and the browser client, let’s add some styling. Adding styles with glamor I chose to use for styling, for css in React. glamor one of many great options Why glamor? It supports server-side rendering despite some hair-pulling CSS modules looked harder to setup server-side Grown to prefer writing css in js because you have at your disposal all the features of javascript Adding glamor to the project involves two-steps: Generate the css stylesheet in the server side render function and pass it to the template component for the initial page load css Rehydrate glamor’s server state browser-side Glamor has it’s own render function for server-side rendering that returns the initial html of the app and any styles that were created during the render. There are related to the order of imports you need to be aware of when server-side rendering with glamor. a couple gotchas Now we just need to rehydrate the server state in the browser: Here’s where you’d start adding a bunch of components but I’m going to skip over creating and styling components and move on to the actual Lambda function. You can see the components I made for my website if you’re curious. in the repo Lambda function For our backend we are going to setup an function to run the webpack compiler and upload the result to an S3 bucket. All the Lambda needs is a javascript function and AWS will provision and manage the resources for you. AWS Lambda Here’s the entire Lambda function: : You can use to to output the webpack build to memory instead of the file system Tip #3 memory-fs After the compilation step the Lambda will upload the files to S3 and invalidate the CloudFront distribution. Alternatively to setting up CloudFront, you can . configure the bucket to be a static website Infrastructure Now we’re ready to start creating the infrastructure. I’m going to use AWS Lambda, S3 and CloudFront for hosting the website and for sending and receiving email with the domain. Mailgun Here’s an overview of the Terraform structure: Terraform is executed from the directory of the environment you want to deploy. In this case we only have one environment and running from env-dev will deploy the dev infrastructure. terraform apply After each deploy, Terraform sill save the state of the the created resources into a file inside the current directory. When working in larger teams, you way want to use . terraform.tfstate remote state Setting up the environment In order to deploy the environment, we need to create it’s . Here’s a condensed version of ours: terraform variable file When you run , it will run in the current directory. Since we are running the command from env-dev there is only one — dev.tf. terraform apply every configuration file The dev configuration file authenticates with AWS and Mailgun then creates the app’s infrastructure. The app module let us create re-usable components of our infrastructure. Terraform can use modules from the filesystem, Github and . This project uses one local module for the entire app but you can compose your infrastructure from as many modules as you’d like. Terraform modules other remote sources To create the app module we’ll start by defining it’s input variables: This setup allows us to easily create more environments in the future like env-staging and env-production. Now we can start creating all the resources for our app. Adding the Lambda function Building the project will package up our app to be uploaded to Lambda. When we deploy, Terraform will determine whether or not to deploy a new version of the handler using the package’s hash. Adding the S3 bucket Create a private S3 bucket for our domain. Scheduling the Lambda with CloudWatch We’ll use to trigger our Lambda every 15 minutes. CloudWatch Events Adding the CloudFront CDN Create a CloudFront distribution for our S3 bucket. Adding the DNS records Assuming you’ve registered your domain in Route 53, we’ll use to fetch the hosted zone for our domain by name and create the DNS entries for our website. data sources Adding Mailgun The fastest way to start sending and receiving email through our domain is to create a account. Retrieve your api key from your account profile and add it to dev.tfvars. Mailgun Thanks to Terraform’s we can create a new domain resource with Mailgun’s API right from our config file: Mailgun provider The resulting terraform output of creating the Mailgun domain will look something like this: Ideally, we could tell Terraform to create a DNS entry for each receiving record and sending record but . Terraform doesn’t currently support using count with computed values Since the number of records is known, we can manually create each record as a workaround: All of our infrastructure is now set up using only terraform config files and you can now run to preview the changes then to deploy them. terraform plan terraform apply You will need to in Mailgun after the first deploy if you don’t want to wait up to 24 hours before you can start receiving emails. trigger domain verification The final step is adding a Mailgun route to forward emails to our main account. Here’s what mine looks like: Mailgun catch-all route for email forwarding. Final thoughts Making changes to infrastructure and spinning up new environments has never been easier with Terraform. I don’t think I’ll be logging into the AWS console anytime soon. You’re more than welcome to create, modify and deploy your own versions of . I’d love to hear any suggestions you have for improvements or cool features I could add for a followup post. the source code