Deploy AWS resources using AWS CloudFormation
This example demonstrates how to deploy an AWS resource using an AWS CloudFormation template, via Port Actions.
We will use an AWS managed GitHub Action called aws-actions/aws-cloudformation-github-deploy.
Stepsโ
- 
Create the following GitHub action secrets: - PORT_CLIENT_ID- Port Client ID learn more.
- PORT_CLIENT_SECRET- Port Client Secret learn more.
- AWS_ACCESS_KEY_ID- AWS credentials.
- AWS_SECRET_ACCESS_KEY- AWS credentials.
- AWS_REGION- AWS region name to deploy your resources to.
 
- 
Install Port's GitHub app by clicking here. 
- 
Create a Port blueprint with the following JSON definition (choose your desired resource): 
- EC2 Instance
- S3 Bucket
- RDS Instance
Port EC2 Instance Blueprint
{
  "identifier": "ec2Instance",
  "description": "AWS EC2 Instance",
  "title": "EC2 Instance",
  "icon": "EC2",
  "schema": {
    "properties": {
      "instance_name": {
        "title": "Instance Name",
        "type": "string"
      },
      "instance_type": {
        "title": "Instance Type",
        "type": "string"
      },
      "image_id": {
        "title": "Image ID",
        "type": "string"
      },
      "key_pair_name": {
        "title": "Key Pair Name",
        "type": "string"
      },
      "security_group_ids": {
        "title": "Security Group IDs",
        "type": "string"
      }
    },
    "required": [
      "instance_name",
      "instance_type",
      "image_id",
      "key_pair_name",
      "security_group_ids"
    ]
  },
  "mirrorProperties": {},
  "calculationProperties": {},
  "relations": {}
}
Port S3 Bucket Blueprint
{
  "identifier": "s3_bucket",
  "title": "S3 Bucket",
  "icon": "S3",
  "schema": {
    "properties": {
      "bucket_name": {
        "title": "Bucket Name",
        "type": "string",
        "minLength": 3,
        "maxLength": 63,
        "icon": "DefaultProperty"
      },
      "bucket_acl": {
        "icon": "DefaultProperty",
        "title": "Bucket ACL",
        "type": "string",
        "default": "Private"
      }
    },
    "required": ["bucket_name", "bucket_acl"]
  },
  "mirrorProperties": {},
  "calculationProperties": {},
  "relations": {}
}
Port RDS Instance Blueprint
{
  "identifier": "rds_instance",
  "title": "RDS Instance",
  "icon": "AmazonRDS",
  "schema": {
    "properties": {
      "db_instance_identifier": {
        "title": "DB Instance Identifier",
        "type": "string",
        "minLength": 1,
        "maxLength": 63,
        "icon": "DefaultProperty"
      },
      "db_master_password": {
        "icon": "DefaultProperty",
        "title": "DB Master Password",
        "type": "string"
      },
      "db_master_username": {
        "title": "DB Master Username",
        "type": "string",
        "minLength": 1,
        "maxLength": 63,
        "icon": "DefaultProperty"
      },
      "db_engine": {
        "title": "DB Engine",
        "type": "string",
        "icon": "DefaultProperty"
      },
      "allocated_storage": {
        "title": "Allocated Storage",
        "type": "number",
        "default": 20,
        "minimum": 5,
        "maximum": 1000,
        "icon": "DefaultProperty"
      },
      "db_instance_class": {
        "title": "DB Instance Class",
        "type": "string",
        "icon": "DefaultProperty"
      }
    },
    "required": [
      "db_instance_identifier",
      "db_master_password",
      "db_master_username",
      "db_engine",
      "allocated_storage",
      "db_instance_class"
    ]
  },
  "mirrorProperties": {},
  "calculationProperties": {},
  "relations": {}
}
- Create Port Action using the following JSON definition:
Please make sure to modify GITHUB_ORG, GITHUB_REPO and GITHUB_WORKFLOW_FILE placeholders to match your environment.
- EC2 Instance
- S3 Bucket
- RDS Instance
Port Action
{
  "identifier": "deploy_ec2_instance",
  "title": "Deploy EC2 Instance",
  "icon": "EC2",
  "trigger": {
    "type": "self-service",
    "operation": "CREATE",
    "userInputs": {
      "properties": {
        "instance_name": {
          "title": "Instance Name",
          "type": "string"
        },
        "instance_type": {
          "title": "Instance Type",
          "type": "string",
          "default": "t2.micro",
          "enum": ["t2.micro", "t2.small"],
          "enumColors": {
            "t2.micro": "lightGray",
            "t2.small": "lightGray"
          }
        },
        "image_id": {
          "title": "Image ID",
          "type": "string"
        },
        "key_pair_name": {
          "title": "Key Pair Name",
          "type": "string"
        },
        "security_group_ids": {
          "title": "Security Group IDs",
          "icon": "DefaultProperty",
          "type": "string",
          "description": "Use comma delimited values for multiple SGs"
        }
      },
      "required": [
        "instance_name",
        "instance_type",
        "image_id",
        "key_pair_name",
        "security_group_ids"
      ],
      "order": [
        "instance_name",
        "instance_type",
        "image_id",
        "key_pair_name",
        "security_group_ids"
      ]
    },
    "blueprintIdentifier": "ec2Instance"
  },
  "invocationMethod": {
    "type": "GITHUB",
    "org": "<GITHUB_ORG>",
    "repo": "<GITHUB_REPO>",
    "workflow": "<GITHUB_WORKFLOW_FILE>",
    "workflowInputs": {
      "instance_name": "{{ .inputs.\"instance_name\" }}",
      "instance_type": "{{ .inputs.\"instance_type\" }}",
      "image_id": "{{ .inputs.\"image_id\" }}",
      "key_pair_name": "{{ .inputs.\"key_pair_name\" }}",
      "security_group_ids": "{{ .inputs.\"security_group_ids\" }}",
      "port_context": {
        "entity": "{{ .entity }}",
        "blueprint": "{{ .action.blueprint }}",
        "runId": "{{ .run.id }}",
        "trigger": "{{ .trigger }}"
      }
    },
    "reportWorkflowStatus": true
  },
  "requiredApproval": false
}
Port Action
{
  "identifier": "create_s3_bucket",
  "title": "Create S3 Bucket",
  "icon": "S3",
  "trigger": {
    "type": "self-service",
    "operation": "CREATE",
    "userInputs": {
      "properties": {
        "bucket_name": {
          "title": "Bucket Name",
          "type": "string",
          "minLength": 3,
          "maxLength": 63
        },
        "bucket_acl": {
          "icon": "DefaultProperty",
          "title": "Bucket ACL",
          "description": "bucket access control list",
          "type": "string",
          "default": "Private",
          "enum": [
            "Private",
            "PublicRead",
            "PublicReadWrite",
            "AuthenticatedRead"
          ],
          "enumColors": {
            "Private": "lightGray",
            "PublicRead": "lightGray",
            "PublicReadWrite": "lightGray",
            "AuthenticatedRead": "lightGray"
          }
        }
      },
      "required": ["bucket_name", "bucket_acl"],
      "order": ["bucket_name", "bucket_acl"]
    },
    "blueprintIdentifier": "s3_bucket"
  },
  "invocationMethod": {
    "type": "GITHUB",
    "org": "<GITHUB_ORG>",
    "repo": "<GITHUB_REPO>",
    "workflow": "<GITHUB_WORKFLOW_FILE>",
    "workflowInputs": {
      "bucket_name": "{{ .inputs.\"bucket_name\" }}",
      "bucket_acl": "{{ .inputs.\"bucket_acl\" }}",
      "port_context": {
        "entity": "{{ .entity }}",
        "blueprint": "{{ .action.blueprint }}",
        "runId": "{{ .run.id }}",
        "trigger": "{{ .trigger }}"
      }
    },
    "reportWorkflowStatus": true
  },
  "requiredApproval": false
}
Port Action
{
  "identifier": "deploy_rds_instance",
  "title": "Deploy RDS",
  "icon": "AmazonRDS",
  "trigger": {
    "type": "self-service",
    "operation": "CREATE",
    "userInputs": {
      "properties": {
        "db_instance_identifier": {
          "title": "DB Instance Identifier",
          "type": "string",
          "minLength": 1,
          "maxLength": 63
        },
        "db_master_password": {
          "title": "DB Master Password",
          "type": "string",
          "encryption": "aes256-gcm"
        },
        "db_master_username": {
          "title": "DB Master Username",
          "type": "string"
        },
        "db_engine": {
          "title": "DB Engine",
          "type": "string",
          "default": "mysql",
          "enum": ["mysql", "postgres", "sqlserver", "oracle"],
          "enumColors": {
            "mysql": "lightGray",
            "postgres": "lightGray",
            "sqlserver": "lightGray",
            "oracle": "lightGray"
          }
        },
        "allocated_storage": {
          "title": "Allocated Storage",
          "type": "number",
          "default": 20,
          "minimum": 5,
          "maximum": 1000
        },
        "db_instance_class": {
          "title": "DB Instance Class",
          "type": "string",
          "default": "db.t3.micro",
          "enum": ["db.t3.micro"],
          "enumColors": {
            "db.t3.micro": "lightGray"
          }
        }
      },
      "required": [
        "db_instance_identifier",
        "db_master_password",
        "db_master_username",
        "db_engine",
        "allocated_storage",
        "db_instance_class"
      ],
      "order": [
        "db_instance_identifier",
        "db_master_username",
        "db_master_password",
        "db_engine",
        "db_instance_class",
        "allocated_storage"
      ]
    },
    "blueprintIdentifier": "rds_instance"
  },
  "invocationMethod": {
    "type": "GITHUB",
    "org": "<GITHUB_ORG>",
    "repo": "<GITHUB_REPO>",
    "workflow": "<GITHUB_WORKFLOW_FILE>",
    "workflowInputs": {
      "db_instance_identifier": "{{.inputs.\"db_instance_identifier\"}}",
      "db_master_password": "{{.inputs.\"db_master_password\"}}",
      "db_master_username": "{{.inputs.\"db_master_username\"}}",
      "db_engine": "{{.inputs.\"db_engine\"}}",
      "allocated_storage": "{{.inputs.\"allocated_storage\"}}",
      "db_instance_class": "{{.inputs.\"db_instance_class\"}}",
      "port_context": {
        "entity": "{{ .entity }}",
        "blueprint": "{{ .action.blueprint }}",
        "runId": "{{ .run.id }}",
        "trigger": "{{ .trigger }}"
      }
    },
    "reportWorkflowStatus": true
  },
  "requiredApproval": false
}
- Create a CloudFormation template file in your GitHub repository:
- EC2 Instance
- S3 Bucket
- RDS Instance
AWS CloudFormation Template
AWSTemplateFormatVersion: '2010-09-09'
Description: CloudFormation Template to Deploy an EC2 Instance
Parameters:
  InstanceName:
    Description: Name for the EC2 instance
    Type: String
    MinLength: 1
    MaxLength: 255
    Default: MyEC2InstanceName
    ConstraintDescription: Instance name must not be empty
  InstanceType:
    Description: EC2 instance type
    Type: String
    Default: t2.micro
    AllowedValues:
      - t2.micro
      - t2.small
      - t2.medium
      # Add more instance types as needed
    ConstraintDescription: Must be a valid EC2 instance type
  ImageId:
    Description: ID of the Amazon Machine Image (AMI) to use
    Type: AWS::EC2::Image::Id
    ConstraintDescription: Must be a valid AMI ID
  KeyPairName:
    Description: Name of the key pair for SSH access
    Type: String
    MinLength: 1
    MaxLength: 255
    ConstraintDescription: Key pair name must not be empty
  SecurityGroupIds:
    Description: List of Security Group IDs for the EC2 instance
    Type: List<AWS::EC2::SecurityGroup::Id>
    ConstraintDescription: Must be a list of valid Security Group IDs
Resources:
  EC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: !Ref InstanceType
      ImageId: !Ref ImageId
      KeyName: !Ref KeyPairName
      SecurityGroupIds: !Ref SecurityGroupIds
      Tags:
        - Key: Name
          Value: !Ref InstanceName
Outputs:
  InstanceId:
    Description: ID of the created EC2 instance
    Value: !Ref EC2Instance
AWS CloudFormation Template
AWSTemplateFormatVersion: '2010-09-09'
Description: CloudFormation Template for an S3 Bucket
Parameters:
  BucketName:
    Description: Name for the S3 bucket
    Type: String
    MinLength: 3
    MaxLength: 63
    ConstraintDescription: The bucket name must be between 3 and 63 characters.
  BucketAcl:
    Description: Access control for the S3 bucket
    Type: String
    Default: Private
    AllowedValues:
      - Private
      - PublicRead
      - PublicReadWrite
      - AuthenticatedRead
    ConstraintDescription: Choose a valid access control option.
Resources:
  S3Bucket:
    Type: 'AWS::S3::Bucket'
    Properties:
      BucketName: !Ref BucketName
      AccessControl: !Ref BucketAcl
Outputs:
  S3BucketName:
    Description: Name of the created S3 bucket
    Value: !Ref S3Bucket
AWS CloudFormation Template
AWSTemplateFormatVersion: '2010-09-09'
Description: CloudFormation Template for an Amazon RDS Instance
Parameters:
  DBInstanceIdentifier:
    Description: Identifier for the RDS instance
    Type: String
    MinLength: 1
    MaxLength: 63
    Default: myrdsinstance
    ConstraintDescription: The DB instance identifier must be between 1 and 63 characters.
  DBMasterUsername:
    Description: Master username for the RDS instance
    Type: String
    MinLength: 1
    MaxLength: 63
    Default: admin
    ConstraintDescription: The master username must be between 1 and 63 characters.
  DBMasterPassword:
    Description: Master password for the RDS instance
    Type: String
    NoEcho: true
    MinLength: 8
    MaxLength: 41
    Default: MySecurePassword
    ConstraintDescription: The master password must be between 8 and 41 characters.
  DBEngine:
    Description: Database engine for the RDS instance
    Type: String
    Default: mysql
    AllowedValues:
      - mysql
      - postgres
      - sqlserver
      - oracle
    ConstraintDescription: Choose a valid database engine.
  AllocatedStorage:
    Description: Allocated storage for the RDS instance (in GB)
    Type: Number
    Default: 20
    MinValue: 5
    MaxValue: 6144
    ConstraintDescription: Allocated storage must be between 5 and 6144 GB.
  DBInstanceClass:
    Description: DB instance class for the RDS instance
    Type: String
    Default: db.t3.micro
    AllowedValues:
      - db.t3.micro
      # Add more instance types as needed
    ConstraintDescription: Choose a valid DB instance class.
Resources:
  RDSInstance:
    Type: 'AWS::RDS::DBInstance'
    Properties:
      DBInstanceIdentifier: !Ref DBInstanceIdentifier
      AllocatedStorage: !Ref AllocatedStorage
      DBInstanceClass: !Ref DBInstanceClass
      Engine: !Ref DBEngine
      MasterUsername: !Ref DBMasterUsername
      MasterUserPassword: !Ref DBMasterPassword
Outputs:
  RDSInstanceEndpoint:
    Description: Endpoint for the created RDS instance
    Value: !GetAtt RDSInstance.Endpoint.Address
- Create a workflow file under .github/workflows/deploy-cloudformation-template.ymlwith the following content:
Please make sure to modify CF_TEMPLATE_FILE placeholder to match the CloudFormation template file path.
- EC2 Instance
- S3 Bucket
- RDS Instance
GitHub workflow
name: Deploy CloudFormation - EC2 Instance
on:
  workflow_dispatch:
    inputs:
      instance_name:
        required: true
        type: string
        description: instance name
      instance_type:
        required: true
        type: string
        description: instance type
      image_id:
        required: true
        type: string
        description: image id
      key_pair_name:
        required: true
        type: string
        description: key pair name
      security_group_ids:
        required: true
        type: string
        description: security group ids
      port_context:
        required: true
        description: Action and general port_context (blueprint, run id, etc...)
        type: string
jobs:
  deploy-cloudformation-template:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Configure AWS Credentials ๐
        id: aws-credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ secrets.AWS_REGION }}
      - name: Deploy to AWS CloudFormation
        uses: aws-actions/aws-cloudformation-github-deploy@v1
        with:
          name: ${{ inputs.instance_name }}
          template: <CF_TEMPLATE_FILE>
          parameter-overrides: >-
            InstanceName=${{ inputs.instance_name }},
            InstanceType=${{ inputs.instance_type }},
            ImageId=${{ inputs.image_id }},
            KeyPairName=${{ inputs.key_pair_name }},
            SecurityGroupIds="${{ inputs.security_group_ids }}"
      - name: UPSERT EC2 Instance Entity in Port
        uses: port-labs/port-github-action@v1
        with:
          identifier: ${{ inputs.instance_name }}
          title: ${{ inputs.instance_name }}
          team: '[]'
          icon: EC2
          blueprint: ec2Instance
          properties: |-
            {
              "instance_name": "${{ inputs.instance_name }}",
              "instance_type": "${{ inputs.instance_type }}",
              "image_id": "${{ inputs.image_id }}",
              "key_pair_name": "${{ inputs.key_pair_name }}",
              "security_group_ids": "${{ inputs.security_group_ids }}"
            }
          relations: '{}'
          clientId: ${{ secrets.PORT_CLIENT_ID }}
          clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
          baseUrl: https://api.getport.io
          operation: UPSERT
          runId: ${{fromJson(inputs.port_context).runId}}
GitHub workflow
name: Deploy CloudFormation - S3 Bucket
on:
  workflow_dispatch:
    inputs:
      bucket_name:
        required: true
        type: string
        description: bucket name
      bucket_acl:
        required: true
        type: string
        description: bucket acl
      port_context:
        required: true
        description: Details of the action and general port_context (blueprint, run id, etc...)
        type: string
jobs:
  deploy-cloudformation-template:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Configure AWS Credentials ๐
        id: aws-credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ secrets.AWS_REGION }}
      - name: Deploy to AWS CloudFormation
        uses: aws-actions/aws-cloudformation-github-deploy@v1
        with:
          name: ${{ inputs.bucket_name }}
          template: <CF_TEMPLATE_FILE>
          parameter-overrides: >-
            BucketName=${{ inputs.bucket_name }},
            BucketAcl=${{ inputs.bucket_acl }}
      - name: UPSERT S3 Bucket Entity in Port
        uses: port-labs/port-github-action@v1
        with:
          identifier: ${{ inputs.bucket_name }}
          title: ${{ inputs.bucket_name }}
          team: '[]'
          icon: S3
          blueprint: s3_bucket
          properties: |-
            {
              "bucket_name": "${{ inputs.bucket_name }}",
              "bucket_acl": "${{ inputs.bucket_acl }}"
            }
          relations: '{}'
          clientId: ${{ secrets.PORT_CLIENT_ID }}
          clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
          baseUrl: https://api.getport.io
          operation: UPSERT
          runId: ${{fromJson(inputs.port_context).runId}}
GitHub workflow
name: Deploy CloudFormation - RDS Instance
on:
  workflow_dispatch:
    inputs:
      db_instance_identifier:
        required: true
        type: string
        description: db_instance_identifier
      db_master_username:
        required: true
        type: string
        description: db_master_username
      db_master_password:
        required: true
        type: string
        description: db_master_password
      db_engine:
        required: true
        type: string
        description: db_engine
      db_instance_class:
        required: true
        type: string
        description: db_instance_class
      allocated_storage:
        required: true
        type: number
        description: allocated_storage
      port_context:
        required: true
        description: Details about the action and general port_context (blueprint, run id, etc...)
        type: string
jobs:
  deploy-cloudformation-template:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Set Up Python
        uses: actions/setup-python@v2
        with:
          python-version: 3.x
      - name: Configure AWS Credentials ๐
        id: aws-credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ secrets.AWS_REGION }}
      - name: Decrypt aes256-gcm String
        id: decrypt_password
        run: |
          pip install --upgrade pip
          pip install pycryptodome
          python decrypt_password.py
        env:
          PORT_CLIENT_SECRET: ${{ secrets.PORT_CLIENT_SECRET }}
          PASSWORD: ${{ inputs.db_master_password }}
      - name: Deploy to AWS CloudFormation
        uses: aws-actions/aws-cloudformation-github-deploy@v1
        with:
          name: ${{ inputs.db_instance_identifier }}
          template: <CF_TEMPLATE_FILE>
          parameter-overrides: >-
            DBInstanceIdentifier=${{ inputs.db_instance_identifier }},
            DBMasterUsername=${{ inputs.db_master_username }},
            DBMasterPassword=${{ steps.decrypt_password.outputs.decrypted_value }},
            DBEngine=${{ inputs.db_engine }},
            DBInstanceClass=${{ inputs.db_instance_class}},
            AllocatedStorage=${{ inputs.allocated_storage }}
      - name: UPSERT RDS Instance Entity in Port
        uses: port-labs/port-github-action@v1
        with:
          identifier: ${{ inputs.db_instance_identifier }}
          title: ${{ inputs.db_instance_identifier }}
          team: '[]'
          icon: RDS
          blueprint: rds_instance
          properties: |-
            {
              "db_instance_identifier": "${{ inputs.db_instance_identifier }}",
              "db_master_username": "${{ inputs.db_master_username }}",
              "db_master_password": "${{ inputs.db_master_password }}",
              "db_engine": "${{ inputs.db_engine }}",
              "db_instance_class": "${{ inputs.db_instance_class }}",
              "allocated_storage": ${{ inputs.allocated_storage }}
            }
          relations: '{}'
          clientId: ${{ secrets.PORT_CLIENT_ID }}
          clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
          baseUrl: https://api.getport.io
          operation: UPSERT
          runId: ${{fromJson(inputs.port_context).runId}}
Create decrypt_password.py file with the following content to decrypt the password values:Decrypt Password Script
import base64
import os
from Crypto.Cipher import AES
key = os.getenv('PORT_CLIENT_SECRET')[:32].encode()
encrypted_property_value = base64.b64decode(os.getenv('PASSWORD'))
iv = encrypted_property_value[:16]
ciphertext = encrypted_property_value[16:-16]
mac = encrypted_property_value[-16:]
cipher = AES.new(key, AES.MODE_GCM, iv)
# decrypt the property
decrypted_property_value = cipher.decrypt_and_verify(ciphertext, mac)
print(f"::set-output name=decrypted_value::{decrypted_property_value}")
- Trigger the action from the Self-service tab of your Port application.
What's next?โ
- Connect Port's AWS exporter to make sure all of the properties and entities are automatically ingested from AWS.