> For the complete documentation index, see llms.txt.
Skip to main content

Check out Port for yourself ➜ 

Deploy S3 Bucket using Crossplane

Implement with AI

Send this guide to your coding agent.

Prerequisite: Install Port MCP

This guide walks you through setting up a workflow that deploys Crossplane resources in your Kubernetes cluster.

The workflow we will create opens a pull request (GitHub) or merge request (GitLab) in your Git project, committing a Crossplane manifest that describes an S3 bucket in AWS. It also registers the bucket in your Port catalog so you can track it from the moment it is requested.

Common use cases​

  • Let developers provision cloud resources without writing Crossplane manifests by hand.
  • Keep every provisioned resource behind code review, since the workflow proposes a change rather than applying one.
  • Track requested buckets, their region, and their manifest pull request from a single catalog page.

Prerequisites​

  • A Port account with permissions to build workflows.
  • A Kubernetes cluster.
  • Crossplane installed in your cluster:
  • A GitOps tool that syncs manifests from your Git project to your cluster. If you do not have one yet, follow our end to end guide for creating Kubernetes resources with Crossplane and ArgoCD.
  • The Git integration relevant for you, with actions processing enabled:

How it works​

The setup has two parts:

  1. The workflow - a self-service trigger node collects the bucket name and region, an upsert entity node registers the bucket in the catalog, and an integration action node triggers your Git provider's pipeline.
  2. The backend - a CI pipeline that renders the Crossplane manifest from a template, opens a pull/merge request with it, and writes the request URL back to the bucket entity.

The manifest reaches your cluster once the request is merged and your GitOps tool syncs it.

GitOps required

This workflow creates a pull/merge request. Without a GitOps tool watching the repository, the manifest is never applied to your cluster.

Set up the data model​

Head over to the Data model page to create the S3 bucket blueprint:

  1. Click on the + Blueprint button.
  2. Click on the {...} Edit JSON button.
  3. Copy and paste the following JSON configuration into the editor.
  4. Click Save.
S3 bucket blueprint (click to expand)
{
"identifier": "s3bucket",
"title": "S3 Bucket",
"icon": "Crossplane",
"schema": {
"properties": {
"aws_region": {
"title": "AWS region",
"icon": "AWS",
"type": "string"
},
"status": {
"title": "Status",
"type": "string",
"enum": ["Manifest requested", "Awaiting merge", "Deployed"],
"enumColors": {
"Manifest requested": "lightGray",
"Awaiting merge": "yellow",
"Deployed": "green"
}
},
"manifest_pr": {
"title": "Manifest pull request",
"type": "string",
"format": "url"
}
},
"required": ["aws_region"]
},
"mirrorProperties": {},
"calculationProperties": {},
"relations": {}
}

The status property moves from Manifest requested to Awaiting merge when the pipeline opens the request. Set it to Deployed from your GitOps tool or a follow-up workflow once the manifest is synced to the cluster.

Set up the backend​

Now we will write the logic that the workflow triggers.

  1. Create a GitHub repository or GitLab project called crossplane_deployer.

    Dedicated pipelines repository

    We recommend keeping the pipelines your workflows trigger in a dedicated repository, separate from your application code.

  2. Inside crossplane_deployer, create a folder named crossplane-templates on the main branch.

  3. Create a template file named s3bucket-crossplane.yaml in that folder:

    crossplane-templates/s3bucket-crossplane.yaml (click to expand)
    apiVersion: s3.aws.upbound.io/v1beta1
    kind: Bucket
    metadata:
    name: BUCKET_NAME
    spec:
    forProvider:
    region: AWS_REGION
    providerConfigRef:
    name: default

    The BUCKET_NAME and AWS_REGION placeholders are replaced by the pipeline at run time.

  4. Add the pipeline and its credentials:

Create the following GitHub Actions secrets in the crossplane_deployer repository:

Then create a workflow file under .github/workflows/create-s3-manifest.yml:

GitHub Actions pipeline (click to expand)
name: Create S3 bucket Crossplane manifest

on:
workflow_dispatch:
inputs:
bucket_name:
description: Name of the S3 bucket
required: true
aws_region:
description: AWS region for the bucket
required: true
port_run_id:
description: Identifier of the Port workflow run
required: true
triggered_by:
description: Email of the user who triggered the workflow
required: false

jobs:
create-manifest:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6

- name: Render the Crossplane manifest
env:
BUCKET_FILE_PATH: manifests/s3bucket
CROSSPLANE_TEMPLATE_PATH: crossplane-templates/s3bucket-crossplane.yaml
run: |
mkdir -p "$BUCKET_FILE_PATH"
BUCKET_FILE_NAME="${BUCKET_FILE_PATH}/s3bucket-${{ inputs.bucket_name }}.yaml"

cp "$CROSSPLANE_TEMPLATE_PATH" "$BUCKET_FILE_NAME"
sed -i "s|BUCKET_NAME|${{ inputs.bucket_name }}|g" "$BUCKET_FILE_NAME"
sed -i "s|AWS_REGION|${{ inputs.aws_region }}|g" "$BUCKET_FILE_NAME"

git add "$BUCKET_FILE_NAME"

- name: Create pull request
id: create-pr
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.CREATOR_TOKEN }}
commit-message: Add ${{ inputs.bucket_name }} S3 bucket Crossplane manifest
committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
branch: deployment/${{ inputs.port_run_id }}
title: "[Deployment] Add ${{ inputs.bucket_name }} S3 bucket Crossplane manifest"
body: |
This pull request was generated by Port.
It contains the Crossplane manifest for the S3 bucket `${{ inputs.bucket_name }}` in `${{ inputs.aws_region }}`.

**Requested by**: ${{ inputs.triggered_by }}.
**Port run ID**: ${{ inputs.port_run_id }}.
labels: |
deployment
automated pr

- name: Report the pull request to Port
if: steps.create-pr.outputs.pull-request-url != ''
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
baseUrl: https://api.port.io
operation: UPSERT
identifier: ${{ inputs.bucket_name }}
title: ${{ inputs.bucket_name }}
blueprint: s3bucket
properties: |-
{
"aws_region": "${{ inputs.aws_region }}",
"status": "Awaiting merge",
"manifest_pr": "${{ steps.create-pr.outputs.pull-request-url }}"
}

Build the workflow​

Now we will create the workflow that registers the bucket and triggers the pipeline.

  1. Go to the workflows page in Port.

  2. Click + Workflow.

  3. Fill out the Create new workflow form, then click Confirm.

  4. Click the {...} button to open the JSON editor and paste the workflow JSON below:

    Modification required

    Replace <GITHUB_ORG> with your GitHub organization, and <YOUR_GITHUB_INTEGRATION_ID> with the installation ID of your GitHub integration from the Data sources page.

    Deploy S3 bucket workflow JSON (click to expand)
    {
    "identifier": "deploy_s3_bucket_crossplane",
    "title": "Deploy S3 bucket using Crossplane",
    "icon": "Crossplane",
    "description": "Commit a Crossplane manifest for a new S3 bucket and open a pull request",
    "nodes": [
    {
    "identifier": "trigger",
    "title": "Request an S3 bucket",
    "config": {
    "type": "SELF_SERVE_TRIGGER",
    "userInputs": {
    "properties": {
    "bucket_name": {
    "type": "string",
    "title": "Bucket name",
    "description": "Has to be globally unique as per AWS limitations"
    },
    "aws_region": {
    "type": "string",
    "title": "AWS region",
    "icon": "AWS",
    "default": "us-east-1",
    "enum": ["us-east-1", "eu-west-1"],
    "enumColors": {
    "us-east-1": "lightGray",
    "eu-west-1": "lightGray"
    }
    }
    },
    "required": ["bucket_name", "aws_region"],
    "order": ["bucket_name", "aws_region"]
    }
    }
    },
    {
    "identifier": "register_bucket",
    "title": "Register bucket in Port",
    "config": {
    "type": "UPSERT_ENTITY",
    "blueprintIdentifier": "s3bucket",
    "mapping": {
    "identifier": "{{ .outputs.trigger.bucket_name }}",
    "title": "{{ .outputs.trigger.bucket_name }}",
    "properties": {
    "aws_region": "{{ .outputs.trigger.aws_region }}",
    "status": "Manifest requested"
    }
    }
    }
    },
    {
    "identifier": "create_manifest_pr",
    "title": "Create manifest pull request",
    "config": {
    "type": "INTEGRATION_ACTION",
    "installationId": "<YOUR_GITHUB_INTEGRATION_ID>",
    "integrationProvider": "github-ocean",
    "integrationInvocationType": "dispatch_workflow",
    "integrationActionExecutionProperties": {
    "org": "<GITHUB_ORG>",
    "repo": "crossplane_deployer",
    "workflow": "create-s3-manifest.yml",
    "workflowInputs": {
    "bucket_name": "{{ .outputs.trigger.bucket_name }}",
    "aws_region": "{{ .outputs.trigger.aws_region }}",
    "port_run_id": "{{ .workflowRun.identifier }}",
    "triggered_by": "{{ .workflowRun.trigger.by.email }}"
    },
    "reportWorkflowStatus": true
    }
    }
    }
    ],
    "connections": [
    {
    "sourceIdentifier": "trigger",
    "targetIdentifier": "register_bucket"
    },
    {
    "sourceIdentifier": "register_bucket",
    "targetIdentifier": "create_manifest_pr"
    }
    ]
    }
  5. Click Apply changes.

Secrets and integration actions

Secrets are not supported with integration actions. Both nodes above authenticate through your installed Git integration, so the pipeline credentials stay in your CI provider rather than in Port.

Execute the workflow​

  1. Head to the Self-service page of your Port application.

  2. Find Deploy S3 bucket using Crossplane and click on it.

  3. Enter a globally unique bucket name, choose an AWS region, and click Execute.

  4. Follow the workflow's progress in the Workflow runs tab. Each node shows its status, and you can expand a node to inspect its output and logs.

  5. Once the run completes, a new pull/merge request is waiting in crossplane_deployer with the S3 bucket manifest, and the bucket appears in your catalog with its status set to Awaiting merge and a link to the request.

Merge the request, and your GitOps tool applies the manifest to your cluster 🚀

Next steps​