Skip to main content

Check out Port for yourself ➜ 

Create An AWS EC2 Instance

This guide demonstrates how to create a self-service action in Port that executes a GitHub workflow to create an EC2 Instance in AWS using Terraform templates.

Workflow documentation links

Prerequisites

  1. A GitHub repository to contain your action resources i.e. the github workflow file.

  2. An AWS Account or IAM user with permission to create access keys. Learn more

  3. An SSH Key Pair to connect with the provisioned instance. Learn more

  4. Install the Ports GitHub app or Port's GitHub ocean.

  5. In your GitHub repository, go to Settings > Secrets and add the following secrets:

    • PORT_CLIENT_ID - Port Client ID learn more
    • PORT_CLIENT_SECRET - Port Client Secret learn more
    • TF_USER_AWS_KEY - An aws access key with the right iam permission to create an ec2 instance learn more
    • TF_USER_AWS_SECRET - An aws access key secret with permission to create an ec2 instance learn more
    • TF_USER_AWS_REGION - The aws region where you would like to provision your ec2 instance.

Set up data model

We need to create a blueprint in Port for the EC2 Instance. Follow the steps below to create the blueprint:

  1. Go to the Builder page of your portal.

  2. Click on + Blueprint.

  3. Click on the {...} button in the top right corner, and choose Edit JSON.

  4. Copy and paste the following JSON schema:

    EC2 Instance Blueprint (click to expand)
    {
    "identifier": "ec2Instance",
    "description": "This blueprint represents an AWS EC2 instance in our software catalog.",
    "title": "EC2 Instance",
    "icon": "EC2",
    "schema": {
    "properties": {
    "instance_state": {
    "type": "string",
    "title": "Instance State",
    "description": "The state of the EC2 instance (e.g., running, stopped).",
    "enum": ["pending", "running", "shutting-down", "terminated", "stopping", "stopped"],
    "enumColors": {
    "pending": "yellow",
    "running": "green",
    "shutting-down": "pink",
    "stopped": "purple",
    "stopping": "orange",
    "terminated": "red"
    }
    },
    "instance_type": {
    "type": "string",
    "title": "Instance Type",
    "description": "The type of EC2 instance (e.g., t2.micro, m5.large)."
    },
    "availability_zone": {
    "type": "string",
    "title": "Availability Zone",
    "description": "The Availability Zone where the EC2 instance is deployed."
    },
    "public_dns": {
    "type": "string",
    "title": "Public DNS",
    "description": "The public DNS name assigned to the EC2 instance."
    },
    "public_ip": {
    "type": "string",
    "title": "Public IP Address",
    "description": "The public IP address assigned to the EC2 instance."
    },
    "private_dns": {
    "type": "string",
    "title": "Private DNS",
    "description": "The private DNS name assigned to the EC2 instance within its VPC."
    },
    "private_ip": {
    "type": "string",
    "title": "Private IP Address",
    "description": "The private IP address assigned to the EC2 instance within its VPC."
    },
    "monitoring": {
    "type": "boolean",
    "title": "Monitoring",
    "description": "Indicates if detailed monitoring is enabled for the EC2 instance."
    },
    "security_group_ids": {
    "type": "array",
    "title": "Security Group IDs",
    "description": "The list of security group IDs assigned to the EC2 instance."
    },
    "key_name": {
    "type": "string",
    "title": "Key Name",
    "description": "The name of the key pair associated with the EC2 instance."
    }
    },
    "required": []
    },
    "mirrorProperties": {},
    "calculationProperties": {},
    "aggregationProperties": {},
    "relations": {}
    }
  5. Click Save to create the blueprint.

Set up GitHub workflow

  1. Create a folder in a directory of your choice within your github repository to host the terraform template files.

  2. Create the following terraform templates ( main.tf, variables.tf and outputs.tf ) within the created folder.

Dedicated Workflows Repository

We recommend creating a dedicated repository for the workflows that are used by Port actions.

main.tf
main.tf
data "aws_ami" "ubuntu" {
most_recent = true

filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/*20.04-amd64-server-*"]
}

filter {
name = "virtualization-type"
values = ["hvm"]
}

owners = ["099720109477"] # Canonical
}

provider "aws" {
region = var.aws_region
}

resource "aws_instance" "app_server" {
ami = data.aws_ami.ubuntu.id
instance_type = var.ec2_instance_type
key_name = var.pem_key_name

tags = {
Name = var.ec2_name
}
}

variables.tf
variable "ec2_name" {
type = string
}

variable "pem_key_name" {
type = string
}

variable "aws_region" {
type = string
}

variable "ec2_instance_type" {
type = string
}
outputs.tf
outputs.tf

output "instance_id" {
description = "The unique identifier for the provisioned EC2 instance."
value = aws_instance.app_server.id
}

output "instance_state" {
description = "The state of the EC2 instance (e.g., running, stopped)."
value = aws_instance.app_server.instance_state
}

output "instance_type" {
description = "The type of EC2 instance (e.g., t2.micro, m5.large)."
value = aws_instance.app_server.instance_type
}

output "availability_zone" {
description = "The Availability Zone where the EC2 instance is deployed."
value = aws_instance.app_server.availability_zone
}

output "public_dns" {
description = "The public DNS name assigned to the EC2 instance."
value = aws_instance.app_server.public_dns
}

output "public_ip" {
description = "The public IP address assigned to the EC2 instance."
value = aws_instance.app_server.public_ip
}

output "private_dns" {
description = "The private DNS name assigned to the EC2 instance within its VPC."
value = aws_instance.app_server.private_dns
}

output "private_ip" {
description = "The private IP address assigned to the EC2 instance within its VPC."
value = aws_instance.app_server.private_ip
}

output "monitoring" {
description = "Indicates if detailed monitoring is enabled for the EC2 instance."
value = aws_instance.app_server.monitoring
}

output "security_group_ids" {
description = "The list of security group IDs assigned to the EC2 instance."
value = aws_instance.app_server.vpc_security_group_ids
}

output "key_name" {
description = "The name of the key pair associated with the EC2 instance."
value = aws_instance.app_server.key_name
}

output "subnet_id" {
description = "The ID of the subnet to which the instance is attached."
value = aws_instance.app_server.subnet_id
}

output "tags" {
description = "A map of tags assigned to the resource."
value = aws_instance.app_server.tags
}
  1. Create a Github Workflow file under .github/workflows/create-an-ec2-instance.yaml with the following content:
GitHub workflow
Replace placeholders

Replace <TERRAFORM-TEMPLATE-DIR> with the directory created to host your terraform templates.

create-an-ec2-instance.yaml
name: Provision AN EC2 Instance

on:
workflow_dispatch:
inputs:
ec2_name:
description: EC2 name
required: true
default: 'App Server'
type: string
ec2_instance_type:
description: EC2 instance type
required: false
default: "t3.micro"
type: string
pem_key_name:
description: EC2 pem key
required: true
type: string
port_context:
required: true
description: includes blueprint, run ID, and entity identifier from Port.
jobs:
provision-ec2:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
with:
node-version: '14'

- name: Log starting of EC2 Instance creation
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
operation: PATCH_RUN
runId: ${{ fromJson(inputs.port_context).run_id }}
logMessage: |
About to create ec2 instance ${{ github.event.inputs.ec2_name }} .. ⛴️

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: '${{ secrets.TF_USER_AWS_KEY }}'
aws-secret-access-key: '${{ secrets.TF_USER_AWS_SECRET }}'
aws-region: '${{ secrets.TF_USER_AWS_REGION }}'

- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_wrapper: false

- name: Terraform Apply
id: apply
env:
TF_VAR_ec2_name: "${{ github.event.inputs.ec2_name }}"
TF_VAR_pem_key_name: "${{ github.event.inputs.pem_key_name}}"
TF_VAR_aws_region: "${{ secrets.TF_USER_AWS_REGION }}"
TF_VAR_ec2_instance_type: "${{ github.event.inputs.ec2_instance_type}}"
run: |
cd <TERRAFORM-TEMPLATE-DIR>
terraform init
terraform validate
terraform plan
terraform apply -auto-approve

- name: Set Outputs
id: set_outputs
run: |
cd <TERRAFORM-TEMPLATE-DIR>
echo "instance_id=$(terraform output -raw instance_id)" >> $GITHUB_ENV
echo "instance_state=$(terraform output -raw instance_state)" >> $GITHUB_ENV
echo "instance_type=$(terraform output -raw instance_type)" >> $GITHUB_ENV
echo "availability_zone=$(terraform output -raw availability_zone)" >> $GITHUB_ENV
echo "public_dns=$(terraform output -raw public_dns)" >> $GITHUB_ENV
echo "public_ip=$(terraform output -raw public_ip)" >> $GITHUB_ENV
echo "private_dns=$(terraform output -raw private_dns)" >> $GITHUB_ENV
echo "private_ip=$(terraform output -raw private_ip)" >> $GITHUB_ENV
echo "monitoring=$(terraform output -raw monitoring)" >> $GITHUB_ENV
security_group_ids_json=$(terraform output -json security_group_ids | jq -c .)
echo "security_group_ids=$security_group_ids_json" >> $GITHUB_ENV
echo "key_name=$(terraform output -raw key_name)" >> $GITHUB_ENV
echo "subnet_id=$(terraform output -raw subnet_id)" >> $GITHUB_ENV
tags=$(terraform output -json tags | jq -c .)
echo "tags=$tags" >> $GITHUB_ENV

- name: Create a log message
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
operation: PATCH_RUN
runId: ${{ fromJson(inputs.port_context).run_id }}
logMessage: |
EC2 Instance created successfully ✅

- name: Report Created Instance to Port
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
baseUrl: https://api.port.io
operation: PATCH_RUN
runId: ${{ fromJson(inputs.port_context).run_id }}
logMessage: "Upserting created EC2 Instance to Port ... "

- name: UPSERT EC2 Instance Entity
uses: port-labs/port-github-action@v1
with:
identifier: "${{ steps.display_outputs.outputs.instance_id }}"
title: "${{ inputs.ec2_name }}"
blueprint: ${{ fromJson(inputs.port_context).blueprint }}
properties: |-
{
"instance_state": "${{ env.instance_state }}",
"instance_type": "${{ env.instance_type }}",
"availability_zone": "${{ env.availability_zone }}",
"public_dns": "${{ env.public_dns }}",
"public_ip": "${{ env.public_ip }}",
"private_dns": "${{ env.private_dns }}",
"private_ip": "${{ env.private_ip }}",
"monitoring": ${{ env.monitoring }},
"security_group_ids": ${{ env.security_group_ids }},
"key_name": "${{ env.key_name }}",
"subnet_id": "${{ env.subnet_id }}",
"tags": ${{ env.tags }}
}
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
baseUrl: https://api.port.io
operation: UPSERT
runId: ${{ fromJson(inputs.port_context).run_id }}

- name: Log After Upserting Entity
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
baseUrl: https://api.port.io
operation: PATCH_RUN
runId: ${{ fromJson(inputs.port_context).run_id }}
logMessage: "Entity upserting was successful ✅"

Set up self-service action

Follow the steps below to create the self-service action:

  1. Go to the self-service page.

  2. Click on the + New Action button.

  3. Click on the {...} Edit JSON button.

  4. Copy and paste the following JSON configuration into the editor:

    Port Action: Create An EC2 Instance (click to expand)
    Modification Required

    Make sure to replace <GITHUB_ORG> and <GITHUB_REPO> with your GitHub organization and repository names respectively.

        {
    "identifier": "create_ec2_instance",
    "title": "Create Instance",
    "icon": "EC2",
    "description": "Create An EC2 Instance from Port",
    "trigger": {
    "type": "self-service",
    "operation": "CREATE",
    "userInputs": {
    "properties": {
    "pem_key_name": {
    "title": "Pem Key Name",
    "description": "EC2 .pem key pair name",
    "icon": "EC2",
    "type": "string"
    },
    "ec2_name": {
    "icon": "EC2",
    "title": "EC2_Name",
    "description": "Name of the instance",
    "type": "string"
    },
    "ec2_instance_type": {
    "title": "EC2 Instance Type",
    "description": "EC2 instance type",
    "icon": "EC2",
    "type": "string",
    "default": "t2.micro",
    "enum": ["t2.micro", "t2.medium", "t2.large", "t2.xlarge", "t2.2xlarge"]
    }
    },
    "required": ["ec2_name", "pem_key_name"],
    "order": ["ec2_name", "ec2_instance_type", "pem_key_name"]
    },
    "blueprintIdentifier": "ec2Instance"
    },
    "invocationMethod": {
    "type": "GITHUB",
    "org": "<GITHUB_ORG>",
    "repo": "<GITHUB_REPO>",
    "workflow": "create-ec2-instance.yaml",
    "workflowInputs": {
    "pem_key_name": "{{.inputs.\"pem_key_name\"}}",
    "ec2_name": "{{.inputs.\"ec2_name\"}}",
    "ec2_instance_type": "{{.inputs.\"ec2_instance_type\"}}",
    "port_context": {
    "blueprint": "{{.action.blueprint}}",
    "entity": "{{.entity.identifier}}",
    "run_id": "{{.run.id}}",
    "relations": "{{.entity.relations}}"
    }
    },
    "reportWorkflowStatus": true
    },
    "requiredApproval": false
    }
  5. Click Save to create the action.

Let's test it!

  1. Head to the Self Service hub
  2. Click on the Create An EC2 Instance action
  3. Fill the pop-up form with details of the EC2 Instance you wish to create
  4. Click on Execute
  5. Wait for the EC2 Instance to be created in AWS