AWS Cloud9 IDE provides a powerful and collaborative environment for software development, fundamentally altering how engineering teams approach infrastructure-as-code and daily coding tasks. Provisioning Cloud9 with Terraform and automating the process with GitHub Actions can streamline the setup, configuration, and maintenance of your development environment. In this article, we explore how to leverage Terraform and GitHub Actions to provision and manage Cloud9 IDE, enabling faster and more efficient development workflows. The integration of these tools allows developers to treat their development environments as disposable, version-controlled assets, eliminating the drift that traditionally occurs between manual setup and automated infrastructure deployment.
Understanding the Cloud9 Ecosystem
AWS Cloud9 is a cloud-based integrated development environment (IDE) that lets you write, run, and debug your code with just a browser. It includes a code editor, debugger, and terminal. Cloud9 comes prepackaged with essential tools for popular programming languages, including JavaScript, Python, PHP, and more, so you don’t need to install files or configure your development machine to start new projects. Since your Cloud9 IDE is cloud-based, you can work on your projects from your office, home, or anywhere using an internet-connected machine.
A critical aspect of the Cloud9 experience is its ability to handle serverless application development. Cloud9 provides a seamless experience for developing serverless applications, enabling you to easily define resources, debug, and switch between local and remote execution of serverless applications. Furthermore, collaboration is a first-class citizen in this ecosystem. With Cloud9, you can quickly share your development environment with your team, enabling you to pair program and track each other's inputs in real time. This real-time visibility is crucial for remote-first engineering organizations where synchronous work is often required.
The underlying architecture of a standard Cloud9 instance relies on Amazon EC2. When you create a Cloud9 environment, an EC2 instance is spawned to host the IDE and your code. You can navigate the console's EC2 instances section, and you will see an instance that was created for your Cloud9 environment. This transparency is a major advantage over proprietary cloud IDEs, as it allows DevOps engineers to apply standard AWS infrastructure management practices, such as Terraform, to the development environment itself.
Prerequisites and Terraform Configuration
Before writing the Terraform code to provision a Cloud9 instance, specific prerequisites must be met. Users should follow the Terraform official guide "Get Started - AWS" for the quick start and installation process for Terraform, configuring it to link your AWS account. This involves ensuring the AWS CLI is configured with valid credentials or that the instance role provides sufficient permissions if running from within AWS.
The core resource used for this provisioning is aws_cloud9_environment_ec2. Below is the foundational Terraform configuration required to instantiate this resource. The following code snippet demonstrates a basic setup, including specific parameters for cost management and network placement.
hcl
resource "aws_cloud9_environment_ec2" "cloudysky_cloud9_instance" {
name = "cloudysky_cloud9_instance"
instance_type = "t2.medium"
automatic_stop_time_minutes = 30
subnet_id = data.aws_subnet.existing_subnet.id
tags = {
Environment = "dev"
Owner = "CloudySky"
}
}
In this configuration, several key arguments are utilized to optimize the environment. The name attribute provides a human-readable identifier for the instance within the AWS Console. The instance_type is set to t2.medium, a common general-purpose instance type suitable for most development tasks. Crucially, the automatic_stop_time_minutes parameter is set to 30. This feature shuts down the instance after 30 minutes since it was last used, significantly reducing operational costs by ensuring that compute resources are not left running idle when developers step away. Finally, the subnet_id is fetched dynamically from a data source, allowing the instance to be placed in a specific network segment, while the tags block ensures proper cost allocation and ownership tracking. For a comprehensive list of available arguments for this resource, developers should consult the official Terraform documentation for aws_cloud9_environment_ec2.
Advanced Moduleization with Terraform
For more complex deployments, relying on a single resource block is often insufficient. Teams require reusable, version-controlled modules that handle network dependencies, security groups, and static IP assignments. The terraform-aws-cloud9 module, for instance, provides a robust way to create an Amazon Cloud9 EC2 Development Environment with an optional elastic IP (EIP).
When using third-party modules, it is essential to understand the dependency graph. A typical implementation using Terragrunt or standard Terraform might look like the following. This example creates a single Cloud9 environment with an optional elastic IP assigned to it, which can be placed inside or outside of your VPC depending on the configuration flags.
```hcl
module "cloud9" {
source = "adamwshero/cloud9/aws"
version = "~> 1.0.5"
name = "testcloud9"
description = "Description of mycloud9"
subnetid = "subnet-123456abcd789"
assignstaticip = true
vpc = true
tags = {
application = "my-service"
environment = "dev"
lastmodifiedby = "[email protected]"
teamname = "devops"
}
}
```
A significant consideration when using this module is cost. Resources in this module can cost money to deploy and maintain. Therefore, it is imperative to size your Cloud9 instances that fit within your budget. The module exposes various inputs and outputs that allow for fine-grained control. The following table outlines the key version constraints and resources managed by a typical implementation of this module.
| Component | Version Constraint | Description |
|---|---|---|
| AWS Provider | >= 2.67.0 | Minimum required version of the AWS Terraform provider |
| Terraform | >= 0.14.0 | Minimum required version of Terraform |
| Terragrunt | >= 0.28.0 | Minimum required version of Terragrunt (if used) |
aws_cloud9_environment_ec2 |
Resource | The primary Cloud9 EC2 environment resource |
aws_eip |
Resource | The Elastic IP resource for static IP assignment |
The module also supports complex tag structures, allowing teams to apply consistent metadata across their infrastructure. In the example above, tags include application, environment, last_modified_by, and team_name. This granularity aids in audit trails and cost reporting. Furthermore, the module can be integrated with dependency management to ensure that the VPC and subnets exist before the Cloud9 environment is provisioned. For example, using dependency "vpc" in a Terragrunt configuration ensures that the subnet_id is pulled from the outputs of a separate VPC module, such as dependency.vpc.outputs.public_subnets[1].
Automating Deployment with GitHub Actions
While Terraform manages the infrastructure state, GitHub Actions provides the automation layer that triggers these changes. By implementing a CI/CD pipeline for Terraform, organizations can enforce code quality standards and automate the deployment of development environments. The following GitHub Actions workflow configuration demonstrates how to integrate Terraform into a standard pull request and push workflow.
The workflow is triggered by pushes to the master branch and pull requests. It runs on ubuntu-latest and requires pull-requests: write permissions to update the status of pull requests.
yaml
name: "Terraform Cloud9 Provisioning"
on:
push:
branches:
- master
pull_request:
jobs:
terraform:
name: "Terraform Cloud9 Provisioning"
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v1
with:
cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }}
- name: Terraform Format
id: fmt
run: terraform fmt -check
- name: Terraform Init
id: init
run: terraform init
- name: Terraform Validate
id: validate
run: terraform validate -no-color
- name: Terraform Plan
id: plan
if: github.event_name == 'pull_request'
run: terraform plan -no-color -input=false
continue-on-error: true
The Terraform Plan step is particularly important for pull requests. It executes terraform plan and captures the output. To visualize this plan within the GitHub interface, the workflow utilizes the actions/github-script action. This script formats the outcome of the format, initialization, validation, and plan steps into a markdown comment.
yaml
- name: Update Pull Request
uses: actions/github-script@v6
if: github.event_name == 'pull_request'
env:
PLAN: ${{ steps.plan.outputs.stdout }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const output = `#### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\`
#### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\`
#### Terraform Validation 🤖\`${{ steps.validate.outcome }}\`
#### Terraform Plan 📖\`${{ steps.plan.outcome }}\`
<details><summary>Show Plan</summary>
\`\`\`terraform
${process.env.PLAN}
\`\`\`
</details>
*Pushed by: @${{ github.actor }}, Action: \`${{ github.event_name }}\`*`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: output
})
This integration allows reviewers to see exactly what infrastructure changes will occur before merging a pull request. If the plan fails, the workflow can be configured to fail the pull request check.
yaml
- name: Terraform Plan Status
if: steps.plan.outcome == 'failure'
run: exit 1
- name: Terraform Apply
if: github.ref == 'refs/heads/master' && github.event_name == 'push'
run: terraform apply -auto-approve -input=false
The Terraform Apply step is only executed on pushes to the master branch. This ensures that infrastructure is only changed when code is merged into the main branch. To make this work, the TF_API_TOKEN must be configured in GitHub secrets, and the Terraform cloud account must be linked to the GitHub repository. By automating the Cloud9 provisioning process with GitHub Actions, you can ensure consistent and reproducible setups for your development environments. This automation saves time and effort, enabling developers to focus on writing code rather than manually setting up environments.
Resolving Credential and Permission Challenges
One of the most common pitfalls when running Terraform inside or alongside a Cloud9 environment is managing AWS credentials. AWS Cloud9 makes temporary AWS access credentials available when we use AWS Cloud9 EC2 development environment. However, you will need to check the Actions supported by AWS managed temporary credentials. In our case of AWS Cloud9 EC2 development environment, we have some limitations.
If you attempt to run terraform apply inside the Cloud9 terminal with these default credentials, you may encounter specific errors. For instance, running terraform apply might throw the following error:
text
╷
│ Error: error configuring Terraform AWS Provider: no valid credential sources for Terraform AWS Provider found.
│
│ Please see https://registry.terraform.io/providers/hashicorp/aws
│ for more information about providing credentials.
│
│ Error: failed to refresh cached credentials, no EC2 IMDS role found, operation error ec2imds: GetMetadata, http response error StatusCode: 404, request to EC2 IMDS failed
│
│
│ with provider["registry.terraform.io/hashicorp/aws"],
│ on providers.tf line 10, in provider "aws":
│ 10: provider "aws" {
│
╵
We get this error because the environment cannot access any AWS services by default if we turn off AWS managed temporary credentials. We need to access AWS services for our Terraform plan to be executed. There are specific actions supported by these temporary credentials, and they may not grant permission for all resources, such as creating IAM roles. For example, you might receive an error like:
text
│ Error: failed creating IAM Role (cognito_authenticated): InvalidClientTokenId: The security token included in the request is invalid
│ status code: 403, request id: cce545a5-25ae-4a65-a73f-ae029e1baa4d
│
│ with aws_iam_role.authenticated,
│ on cognito.tf line 9, in resource "aws_iam_role" "authenticated":
│ 9: resource "aws_iam_role" "authenticated" {
To address these permission issues, there are several alternatives. One approach is attaching an instance profile to the Amazon EC2 instance that connects to our Cloud9 environment. This allows the instance to assume a role with the necessary permissions to execute Terraform commands. Alternatively, if you are using Cloud9, you don't necessarily need to manage complex profiles manually because the terminal includes sudo privileges to the managed Amazon EC2 instance that hosts our development environment.
In many cases, simplifying the provider configuration is sufficient. If you are using a profile that is not strictly necessary or conflicts with the instance role, you should remove the profile = "aws-terraform-example" from the aws provider in providers.tf. Your file should look as follows:
```hcl
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.8"
}
}
}
provider "aws" {
region = var.aws_region
}
```
After making this change, running terraform plan should succeed. Now, run the terraform apply command. If the issue persists regarding specific resource creation, it may be due to the limited scope of the temporary credentials provided by Cloud9. In such scenarios, using an external CI/CD system like GitHub Actions, which uses its own credentials (such as the TF_API_TOKEN and GitHub App secrets), is a more robust solution. This decouples the development environment's limited permissions from the infrastructure provisioning process, ensuring that the pipeline can manage resources with the required IAM permissions.
Conclusion
The integration of Terraform and GitHub Actions with AWS Cloud9 represents a significant advancement in developer experience and infrastructure management. By treating the development environment as a first-class citizen in the infrastructure-as-code pipeline, teams can eliminate configuration drift, reduce costs through automatic stopping of instances, and ensure that every developer has access to an identical, pre-configured environment.
The depth of customization available through Terraform modules allows for complex scenarios, such as assigning static IPs and managing VPC dependencies, while the automation layer of GitHub Actions ensures that these changes are applied consistently and only when appropriate. The ability to review Terraform plans directly in pull requests adds a layer of safety and transparency to infrastructure changes. Furthermore, understanding the nuances of AWS credential management within Cloud9, such as the limitations of temporary credentials and the utility of instance profiles, is critical for troubleshooting permission errors.
As organizations continue to adopt cloud-native development workflows, the synergy between these tools will become increasingly important. The ability to provision, modify, and tear down development environments programmatically not only saves time but also fosters a culture of automation and reproducibility. Developers can focus on innovation and code quality, knowing that the underlying infrastructure is managed, monitored, and updated through a secure, automated pipeline. This approach is not just a technical convenience but a strategic advantage in maintaining scalable, secure, and efficient engineering operations.