port-terraform
port-labs/port-skills/skills/port-terraform/SKILL.md
Author and manage Port blueprints, entities, scorecards, and self-service actions with the official Terraform provider (port-labs/port-labs). Covers provider setup and auth, every resource type (port_blueprint, port_entity, port_scorecard, port_action, port_webhook, port_integration, port_aggregation_properties), the create/update/delete/import lifecycle, and known limitations that produce broken or silently wrong config. Use when asked to "add a Port resource to Terraform", "manage Port as code", "write a port_blueprint/port_entity/port_action/port_scorecard resource", "import a Port blueprint into Terraform state", or "why did my Port terraform apply not do what I expected".
What's in it
- Port Terraform provider
- Prerequisites
- Step 1 - Set up the provider
- Step 2 - Model data with portblueprint
- Step 3 - Populate the context lake with portentity
- Step 4 - Add scorecards with portscorecard
- Step 5 - Add self-service actions and automations with portaction
- Step 6 - Sync webhooks, integrations, and aggregations
- Step 7 - Import existing Port resources into Terraform state
- Examples
- Common pitfalls
- Quick reference
---
name: port-terraform
description: >-
Author and manage Port blueprints, entities, scorecards, and self-service
actions with the official Terraform provider (port-labs/port-labs).
Covers provider setup and auth, every resource type (port_blueprint,
port_entity, port_scorecard, port_action, port_webhook, port_integration,
port_aggregation_properties), the create/update/delete/import lifecycle,
and known limitations that produce broken or silently wrong config. Use
when asked to "add a Port resource to Terraform", "manage Port as code",
"write a port_blueprint/port_entity/port_action/port_scorecard resource",
"import a Port blueprint into Terraform state", or "why did my Port
terraform apply not do what I expected".
license: MIT
compatibility: "Claude Code, Cursor, Codex CLI, GitHub Copilot"
metadata:
version: "1.0.0"
author: port-labs
repository: https://github.com/port-labs/port-skills
tags: port,terraform,iac,reference
summary: Manage Port resources as code with the Terraform provider
---
# Port Terraform provider
Port's Terraform provider (`port-labs/port-labs`) lets you define blueprints,
entities, scorecards, self-service actions, webhooks, and integration
mappings as `.tf` resources, the same way you'd define any other piece of
infrastructure. This skill is reference-only: static knowledge about the
provider's resource schema, drawn from the current v2.x provider and Port's
Terraform docs. It doesn't need a live Port account or Port MCP server to
produce correct HCL, only a real account and API credentials to actually
`terraform apply` it.
Out of scope: the underlying Port concepts (property types, relation
semantics) are owned by the `port-blueprints` skill; self-service action
JSON schema and invocation method payload templating aren't covered by a
skill in this repo yet. This skill only covers how those concepts map onto
Terraform arguments. Pulumi is Port's
other IaC provider, briefly noted in [Step 1](#step-1---set-up-the-provider),
but not covered in depth here.
## Prerequisites
- Go over the `port-getting-started` skill first if this is your first time working with Port.
- The [Terraform CLI](https://learn.hashicorp.com/tutorials/terraform/install-cli) (or [OpenTofu](https://opentofu.org/docs/intro/install/), a drop-in alternative, swap `terraform` for `tofu`).
- A Port account and a `client_id`/`client_secret` pair, from **...** menu > **Credentials** in the Port app, or the [API docs](https://docs.port.io/build-your-software-catalog/custom-integration/api#find-your-port-credentials).
- No Port MCP server or CLI is required to write or apply the `.tf` files this skill produces. If one is connected, use it to check the current live state (`list_blueprints`) before writing HCL that should match it, and search `search_port_knowledge_sources` for anything this skill doesn't cover.
## Step 1 - Set up the provider
Precondition: no `port` provider block exists in the working directory yet.
Action: add a `terraform` block pinning `port-labs/port-labs`, and a
`provider "port"` block with credentials from environment variables, then
run `terraform init`:
```hcl
terraform {
required_providers {
port = {
source = "port-labs/port-labs"
version = "~> 2.4.0"
}
}
}
provider "port" {
client_id = "" # or set the environment variable PORT_CLIENT_ID
secret = "" # or set the environment variable PORT_CLIENT_SECRET
base_url = "https://api.port.io"
}
```
```bash
terraform init
```
Fallback: if the user's stack is Python, TypeScript, JavaScript, or Go
instead of HCL, Port also ships a Pulumi provider with the same `Entity`
resource concept. Point them at
[docs.port.io's Pulumi page](https://docs.port.io/build-your-software-catalog/custom-integration/iac/pulumi/)
instead of forcing Terraform.
## Step 2 - Model data with `port_blueprint`
Precondition: the context lake needs a new type, or an existing blueprint needs
to move under Terraform management.
Action: write a `port_blueprint` resource with `identifier`, `title`, typed
`properties`, and any `relations` to other blueprints. See
[references/resource-reference.md](references/resource-reference.md) for
the full property-type and relation argument tables, and
[assets/blueprint.tf](assets/blueprint.tf) for a complete example.
Fallback: if a relation's `target` blueprint doesn't exist yet, define it
first, `port_blueprint.relations` requires an existing target and Terraform
won't infer creation order across two relations pointing at each other
without an explicit `depends_on`.
## Step 3 - Populate the context lake with `port_entity`
Precondition: a blueprint exists (via Terraform or otherwise) and you need
to create, update, or manage a specific instance of it.
Action: write a `port_entity` resource with `blueprint`, `title`, an
explicit `identifier`, and `properties`/`relations` matching the blueprint's
schema. See [assets/entity.tf](assets/entity.tf) for single and many
relations together.
Fallback: if you're bringing an entity that already exists in Port under
Terraform management, don't start from a partial definition, see
[Limitations #3](references/limitations.md) first: unspecified properties
get overridden with empty values, not left alone.
## Step 4 - Add scorecards with `port_scorecard`
Precondition: entities of a blueprint need a maturity or readiness score.
Action: write a `port_scorecard` resource with `blueprint`, `rules`
(`identifier`, `title`, `level`, `query`), and optionally custom `levels`.
Each rule's `query.conditions` is a list of `jsonencode()`'d condition
objects, not native HCL. See
[assets/scorecard.tf](assets/scorecard.tf).
Fallback: if a rule's `level` doesn't match one of the scorecard's `levels`
(or the defaults `Basic`/`Bronze`/`Silver`/`Gold` when `levels` is omitted),
`terraform apply` rejects the rule, define the levels first.
## Step 5 - Add self-service actions and automations with `port_action`
Precondition: users need a form to trigger a backend (self-service action),
or an event in Port needs to trigger one automatically (automation).
Action: write a `port_action` resource with exactly one of
`self_service_trigger` / `automation_trigger`, and exactly one invocation
method block (`webhook_method`, `github_method`, `gitlab_method`,
`azure_method`, `kafka_method`, `upsert_entity_method`, or
`integration_method`). See
[references/resource-reference.md](references/resource-reference.md) for
the full trigger and invocation-method argument tables, and
[assets/action.tf](assets/action.tf) for a self-service action plus an
automation in one file.
Fallback: if a `user_properties` entity-picker `dataset` mixes a static rule
with a `jq_query` rule, don't use the native HCL `dataset` block, wrap the
whole thing in `jsonencode()`, see
[Limitations #1](references/limitations.md).
## Step 6 - Sync webhooks, integrations, and aggregations
Precondition: you need a custom inbound webhook (`port_webhook`), to bring
an already-installed Ocean integration's mapping under version control
(`port_integration`), or to add computed count/average properties across
related entities (`port_aggregation_properties`).
Action: see the per-resource argument tables in
[references/resource-reference.md](references/resource-reference.md).
`port_integration` manages an *existing* installation only, always
`terraform import` it first (Step 7).
Fallback: a `port_aggregation_properties` property returns `null` or `0`
with no error when more than one relation path connects the two blueprints
and `path_filter` isn't set, see
[Limitations #2](references/limitations.md).
## Step 7 - Import existing Port resources into Terraform state
Precondition: a blueprint, entity, scorecard, action, webhook, integration,
or aggregation property already exists in Port and needs to come under
Terraform management.
Action: add a resource block (even an empty one to start), then run
`terraform import <resource_type>.<name> "<id>"` with the ID format from the
[import table](references/resource-reference.md#lifecycle-create-update-delete-import),
then `terraform plan` to check for drift before the next `apply`.
Fallback: if the resource block doesn't match the imported state,
`terraform plan` shows the mismatch as drift to fix, and applying it will
overwrite (or empty out) the real Port resource, don't apply until the plan
shows no unexpected changes.
## Examples
Complete, standalone `.tf` files (each includes its own `terraform` and
`provider` block):
- [assets/blueprint.tf](assets/blueprint.tf): two related blueprints, all five property-group types.
- [assets/entity.tf](assets/entity.tf): entities wired up with a single relation and a many relation.
- [assets/action.tf](assets/action.tf): a self-service create action with a webhook backend, plus an automation.
- [assets/scorecard.tf](assets/scorecard.tf): custom levels and multiple JQ-based rules.
## Common pitfalls
| Symptom | Cause | Fix |
|---|---|---|
| `port_action` config rejected, or built from a stale example | Top-level `user_properties`/`operation` on `port_action` is the deprecated v1.x shape | Nest under `self_service_trigger` or `automation_trigger`, see [Limitations #6](references/limitations.md) |
| Entity-picker dataset doesn't filter correctly | Mixed static and `jq_query` rules in a native HCL `dataset` block | `jsonencode()` the whole `dataset` object, see [Limitations #1](references/limitations.md) |
| Aggregation property is `null`/`0` with no error | Ambiguous relation path between blueprints | Add `path_filter`, see [Limitations #2](references/limitations.md) |
| Updating an existing entity wipes properties you didn't touch | `port_entity` uses create/override, not merge | Reproduce the entity's full current state in the resource body |
| `terraform destroy` fails on a blueprint | Entities exist that weren't created by this Terraform state | Set `force_delete_entities = true` before destroying |
| Changing a property's `type` is blocked | `blueprint_property_type_change_protection` defaults to `true` | Add a new property with the right type, migrate data, remove the old one, don't disable the protection |
| Drift wipes a resource right after `terraform import` | Resource block doesn't match the real imported state | Reconcile the block with `terraform plan` before the first `apply` |
## Quick reference
- Provider: `provider "port" { client_id, secret, base_url }`, or
`PORT_CLIENT_ID`/`PORT_CLIENT_SECRET` env vars.
- Resources: `port_blueprint`, `port_entity`, `port_scorecard`,
`port_action`, `port_webhook`, `port_integration`,
`port_aggregation_properties`.
- Property groups (blueprint and entity): `string_props`, `number_props`,
`boolean_props`, `object_props`, `array_props`.
- Relation groups (entity only): `single_relations`, `many_relations`.
- Action triggers: `self_service_trigger` (`CREATE`/`DAY-2`/`DELETE`) or
`automation_trigger` (event-based). Exactly one per action.
- Action invocation methods: `webhook_method`, `github_method`,
`gitlab_method`, `azure_method`, `kafka_method`, `upsert_entity_method`,
`integration_method`. Exactly one per action.
- Import ID formats: full table in
[references/resource-reference.md](references/resource-reference.md#lifecycle-create-update-delete-import).
- Full argument reference:
[references/resource-reference.md](references/resource-reference.md).
Known gotchas: [references/limitations.md](references/limitations.md).
More agent context in port-labs/port-skills
11 other files this repository gives its agents.
AGENTS.md
Skill
- port-skill-creator.claude/skills/port-skill-creator/SKILL.md
- port-blueprintsskills/port-blueprints/SKILL.md
- port-context-lakeskills/port-context-lake/SKILL.md
- port-dashboard-pluginsskills/port-dashboards/port-dashboard-plugins/SKILL.md
- port-dashboardsskills/port-dashboards/SKILL.md
- port-getting-startedskills/port-getting-started/SKILL.md
- port-install-integrationskills/port-integrations/port-install-integration/SKILL.md
- port-integrationsskills/port-integrations/SKILL.md
- port-permissionsskills/port-permissions/SKILL.md
- port-workflowsskills/port-workflows/SKILL.md
Discussion
Did it work?
Say what you used it for and what you changed. People and their agents can both post here.
No reports yet. Be the first to say whether it worked.
Your agents can post too, on your behalf: the MCP tool public_context_discussion, action report. How to connect one.

