> ## Agent Instructions > When helping a user operate an existing Porter project—deploying, updating, debugging, or inspecting applications and clusters—connect to [Porter's remote MCP server](https://mcp.porter.run) first and use its tools before falling back to the Porter CLI, `porter.yaml`, or dashboard workflows. If the Porter tools are not available in your session, `curl -fsSL https://agents.porter.run | sh` installs the CLI, registers the MCP server with your client, and installs Porter's skills; the client must be restarted afterward. If the user asks for the CLI, `porter apply`, configuration-as-code, or the dashboard, use that instead—a stated preference wins. If the requested application or cluster workflow is not directly supported, inspect the relevant state with MCP tools before explaining the limitation or recommending a fallback. The server signs in through a browser using OAuth, so it cannot be used in headless environments such as CI pipelines; use the CLI or `porter.yaml` there. # Custom Helm Charts Source: https://docs.porter.run/addons/custom-helm-charts Deploy external Helm charts to your Porter cluster via the dashboard or Helm CLI for third-party components not natively managed by Porter Sometimes you need to install external charts that are not managed by Porter. You can deploy custom Helm charts either through the Porter dashboard or directly via the Helm CLI. *** ## Deploying via Porter Deploy Helm charts through the Porter dashboard for a managed experience with visibility into your deployments. Navigate to **Add-ons** in your Porter dashboard and select **Helm Chart**. Enter the Helm repository URL and select the chart you want to deploy. Choose the chart version you want to install. Modify any values from the chart's default configuration as needed. Click **Deploy** to install the chart to your cluster. Since custom Helm charts install external components into your cluster, they fall outside of Porter's standard support. However, we'll do our best to help you troubleshoot issues. ### Managing deployed charts Once deployed, you can: * View the chart status in the Add-ons tab * Update values and redeploy * Upgrade to newer chart versions * Delete the chart when no longer needed *** ## Deploying via Helm CLI For more control or CI/CD integration, you can deploy Helm charts directly using the Helm CLI. ### Prerequisites 1. Install the [Helm CLI](https://helm.sh/docs/intro/install/) 2. Configure kubectl to connect to your Porter cluster. Run: ```bash theme={null} porter config set-cluster ``` And select the cluster from the dropdown. If there is only one cluster in your project it will be automatically selected. ### Deploying a chart ```bash theme={null} # Add the Helm repository helm repo add helm repo update # Install the chart: porter helm -- install / \ --namespace \ --values values.yaml ``` ### Example: Installing NGINX Ingress ```bash theme={null} # Add the ingress-nginx repository porter helm -- repo add ingress-nginx https://kubernetes.github.io/ingress-nginx porter helm -- repo update # Install the chart porter helm -- install my-ingress ingress-nginx/ingress-nginx \ --namespace ingress \ --create-namespace ``` ### Upgrading a release ```bash theme={null} porter helm -- upgrade / \ --namespace \ --values values.yaml ``` ### Listing releases ```bash theme={null} porter helm -- list --all-namespaces ``` ### Uninstalling a release ```bash theme={null} porter helm -- uninstall --namespace ``` *** ## Observability There is limited observability offered for third-party helm chart installations, consisting of logs available in the dashboard. For more complex scenarios, you can deploy a **Grafana** addon, which is already configured with the existing observability stack deployed in the cluster. Grafana makes it possible to explore, query and build dashboard to monitor any charts or workloads deployed in your cluster. *** ## Best Practices ### Use version pinning Always specify a chart version to ensure reproducible deployments: ```bash theme={null} porter helm -- install my-release repo/chart --version 1.2.3 ``` ### Store values in version control Keep your custom values files in version control alongside your application code. For example, for the "custom-chart" helm chart, you can keep the following structure: `porter-custom-helm-chart-addons/custom-chart/chart.yaml` ```yaml theme={null} # chart.yaml chartUrl: https://custom-chart-repo.com version: 1.0.0 ``` `porter-custom-helm-chart-addons/custom-chart/values.yaml` ```yaml theme={null} # values.yaml replicaCount: 3 resources: limits: cpu: 100m memory: 128Mi ``` You can then use these values in CI to install the chart ```bash theme={null} CHART_URL=$(yq e '.chartUrl' porter-custom-helm-chart-addons/custom-chart/chart.yaml) VERSION=$(yq e '.version' porter-custom-helm-chart-addons/custom-chart/chart.yaml) porter helm -- install custom-chart $CHART_URL \ --version $VERSION \ --values porter-custom-helm-chart-addons/custom-chart/values.yaml ``` ### Test in non-production first Before deploying to production, test custom charts in a development or staging cluster to verify compatibility with your Porter environment. # Datastores Source: https://docs.porter.run/addons/datastores Provision managed Postgres and Redis databases on AWS with automatic VPC peering, private subnet networking, and security group configuration Porter simplifies database provisioning by automatically setting up all networking components between your cluster and Porter-provisioned databases. Datastores are currently supported on **AWS only**. GCP and Azure datastore support is on the roadmap. ## AWS Architecture Datastores are provisioned in a VPC that is separate from the VPC of your clusters. Porter automatically: * Peers the datastore VPC to your cluster VPC * Configures subnets, routing tables, and security groups * Ensures traffic flows exclusively through private subnets This architecture keeps your database secure and accessible only from applications running in your cluster. *** ## Setup Navigate to **Add-ons** in your Porter dashboard and select the datastore type you want to create (Postgres or Redis). Configure your datastore settings including instance size, storage, and high availability options. Porter creates an environment group with the connection details. Inject this environment group into your applications. Deploy your application. It can now connect to the database using the injected environment variables. ### Connecting from your laptop To connect to a datastore from your local machine, use the Porter CLI: ```bash theme={null} porter datastore connect my-datastore psql -h localhost -p -U -l ``` If your cluster control plane access is set to private, using this command requires [Tailscale VPN](/security-and-compliance/tailscale) to be configured for your cluster. *** ## Postgres Postgres datastores can be deployed in different configurations depending on your needs: | Configuration | Use Case | Recommended For | | ---------------------------------- | ------------------------------------------ | ------------------------------------------------------------------ | | **In-cluster** | Quick setup for development | Dev/staging environments | | **Single RDS instance (Multi-AZ)** | Standard managed database | Production workloads | | **Aurora cluster (Multi-AZ)** | Auto-scaling storage and enhanced failover | Production workloads with stringent high-availability requirements | ### In-cluster Postgres Deploys Postgres as a container within your cluster. This is the fastest way to get started but is **not recommended for production data**. ### RDS Instance Provisions a standard Amazon RDS instance with Multi-AZ deployment for automatic failover. This is the recommended option for most production workloads. ### Aurora Cluster Aurora provides: * Automatic storage autoscaling * Enhanced failover capabilities * High availability settings You can create an Aurora datastore with a single instance or with an additional read replica. #### Read Replicas To enable a read replica, select the **HA toggle** when creating the datastore. With read replicas: * The dashboard displays connection details for both primary and replica * Modifications automatically failover the primary and promote the replica * This ensures minimum downtime during operations ### Configuration The following table outlines the configurable fields and behaviors for each datastore type during creation and updates: | Configuration | In-cluster | Standard RDS | Aurora | | :-------------------- | :----------------------------- | :----------------------------- | :------------------------------------------------ | | **Connected cluster** | Local (via K8s Service) | External (via VPC Peering) | External (via VPC Peering) | | **Region** | Matches connected cluster | Matches connected cluster | Matches connected cluster | | **Database name** | - | User-defined | User-defined | | **Master username** | - | User-defined | User-defined | | **Postgres version** | - | Postgres 12-18 | Postgres 12-18 | | **Instance type** | CPU/RAM Limits | All RDS compatible instances | All Aurora compatible classes, excepts serverless | | **Allocated storage** | **Fixed** (Cannot be modified) | **Modifiable** (Increase only) | **Managed** (Auto-scales) | | **Snapshot restore** | - | From RDS snapshot | From Aurora snapshot | | **Cloning** | - | - | Fast Database Cloning | *** ## Redis Redis datastores can be provisioned in different configurations: | Configuration | Use Case | Recommended For | | --------------------------------- | ------------------------------------- | ------------------------ | | **In-cluster** | Quick setup for development | Dev/staging environments | | **Elasticache replication group** | Managed cache with automatic failover | Production workloads | ### In-cluster Redis Deploys Redis as a container within your cluster. This is the fastest way to get started but is **not recommended for production data**. ### Elasticache Replication Group Provisions an Amazon Elasticache replication group with: * Primary and reader replica by default * Automatic failover if the primary fails * Minimal downtime during modifications *** ## Monitoring You can monitor the performance of your database from the Porter dashboard. Metrics are available in the "Metrics" tab when opening a datastore. The metrics that are currently displayed are: * CPU utilization * RAM utilization * Storage capacity *** ## Disaster Recovery Porter supports some options for disaster recovery of RDS datastores. ### Restoring from a snapshot You can restore a snapshot to a new datastore, and the datastore will be accessible from the applications running in your cluster. This can significantly reduce the time to recovery during an emergency. Create a new datastore in the dashboard. In the creation form, click on **"Enable snapshot restore"** Select one of the available snapshots to restore or enter the id manually. Only snapshots in the same region as the database are listed. Create the datastore. This will start the process of restoring the snapshot. ### Cloning an Aurora cluster Aurora clusters support cloning an existing cluster using fast-cloning. This process is faster than restoring from a snapshot, and can be used to recover from an emergency, or to quickly create copies of your database for experiments. Create a new datastore in the dashboard. In the creation form, click on **"Enable database cloning"** Select one of the existing Aurora clusters or enter the identifier manually. Only clusters in the same region as the selected one are listed. Create the datastore. This will start the process of cloning the cluster. *** ## Compliance If the compliance feature is enabled for your project, Porter automatically configures monitoring alarms for RDS and Aurora datastores: * CPU utilization alarms * Memory utilization alarms * Storage capacity alarms These alarms help ensure your databases remain healthy and within operational thresholds. ## Roadmap The following features are not yet supported natively in Porter, but reach out to the support team for help setting them up. * Connection pooling * External access to datastores # Add-ons Source: https://docs.porter.run/addons/overview Extend your Porter cluster with managed Postgres and Redis databases, persistent storage, third-party monitoring tools, and custom Helm charts Add-ons extend your Porter cluster with additional infrastructure components like databases, monitoring tools, persistent storage, and custom Helm charts. ## Available Add-ons ### Databases | Add-on | Description | | ------------ | ------------------------------------ | | **Postgres** | An object-relational database system | | **Redis** | An in-memory key-value database | [Learn more about Datastores →](/addons/datastores) ### Monitoring | Add-on | Description | | ----------------------- | ---------------------------------------------------- | | **Datadog** | Pipe logs, metrics, and APM data from your workloads | | **New Relic** | Monitor your applications and infrastructure | | **Grafana** | An open source analytics and monitoring tool | | **Langfuse** | An open source LLM engineering platform | | **Helicone AI Gateway** | An open source AI gateway for LLM requests | [Learn more about Third-party Observability →](/addons/third-party-observability) ### Logging | Add-on | Description | | --------- | ----------------------------------- | | **Mezmo** | A popular logging management system | ### Analytics | Add-on | Description | | ------------ | --------------------------------------------- | | **Metabase** | An open source business intelligence tool | | **Quivr** | Your second brain, empowered by generative AI | | **n8n** | An open source workflow engine | ### Storage | Add-on | Description | | ------------------- | --------------------------------------------------------------- | | **Persistent Disk** | A persistent disk that can be attached to apps for data storage | [Learn more about Storage →](/addons/storage) ### Custom | Add-on | Description | | -------------- | ----------------------------------------------- | | **Helm Chart** | Install any Helm chart from a public repository | [Learn more about Custom Helm Charts →](/addons/custom-helm-charts) *** ## Cloud Provider Support | Add-on | AWS | GCP | Azure | | -------------------------------- | --- | ----------- | ----------- | | **Datastores (Postgres, Redis)** | ✅ | Coming soon | Coming soon | | **Monitoring & Analytics** | ✅ | ✅ | ✅ | | **Custom Helm Charts** | ✅ | ✅ | ✅ | | **Storage (Persistent Disk)** | ✅ | Coming soon | Coming soon | *** ## Managing Add-ons Add-ons are managed through the **Add-ons** tab in your Porter dashboard. From there you can: * Create new add-ons * View and modify existing add-on configurations * Connect add-ons to your applications via environment groups * Delete add-ons when no longer needed Deleting an add-on may cause connected applications to lose access to the resource. Ensure you've migrated any data before deleting datastores or storage add-ons. # Persistent storage Source: https://docs.porter.run/addons/storage Provision Amazon EFS persistent disks that can be shared across multiple services for read-intensive and media processing workloads Porter makes it easy to provision persistent storage that can be shared across multiple services. This is useful for read-intensive applications and workloads with low-latency read requirements. Persistent storage is currently supported on **AWS only** (EFS). GCP and Azure storage support is on the roadmap. *** ## Persistent Disk (EFS) Amazon Elastic File System (EFS) provides a shared filesystem that multiple services can mount simultaneously. Any services mounting the disk can read and write to the same files. ### Use Cases * **Shared file storage**: Multiple services need access to the same files * **Read-intensive workloads**: Cache frequently accessed data on disk * **Media processing**: Store uploaded files for processing by multiple workers * **Machine learning**: Share model files across inference services ### Setup Navigate to **Add-ons** in your Porter dashboard and create a **Persistent Disk** add-on. Go to your application's **Services** tab, select the service, and navigate to **Advanced Settings**. Enable **Persistent disk**. Enter the name of the persistent disk add-on you created. Deploy your application. The disk will be mounted automatically. ### Accessing the Disk Once configured, your service can access the shared disk at: ``` /data/efs/ ``` All services with the persistent disk enabled will have read and write access to this directory. ### Example If you have two services (`api` and `worker`) both mounting the same persistent disk 'my-disk': * `api` writes a file to `/data/api/my-disk/uploads/image.png` * `worker` can read the same file from `/data/worker/my-disk/image.png` *** ## Best Practices ### Use for appropriate workloads EFS is optimized for throughput rather than IOPS. It's best suited for: * Large file reads and writes * Shared access patterns * Workloads that can tolerate slightly higher latency than local disk For high-IOPS workloads like databases, use [Datastores](/addons/datastores) instead. ### Monitor storage usage Keep track of storage usage to avoid unexpected costs. EFS charges based on the amount of data stored. ### Plan for data migration If you need to move data off EFS, plan your migration strategy before deleting the add-on. *** ## Deleting a Persistent Disk Deleting the persistent disk add-on will remove all mounts to the disk. Any applications will lose access to the files stored on the disk. Before deleting: 1. Migrate any important data to another storage location 2. Update your services to remove the persistent disk configuration 3. Redeploy affected services 4. Delete the persistent disk add-on # Third-party observability Source: https://docs.porter.run/addons/third-party-observability Integrate Datadog, New Relic, Grafana, Langfuse, and Helicone with your Porter cluster for advanced application monitoring and observability Porter integrates with popular observability platforms to provide application-level monitoring, logging, and alerting beyond the built-in cluster observability features. ## Supported Platforms Full-stack monitoring with APM, logs, and infrastructure metrics Application performance monitoring and alerting Dashboards and visualization for metrics and logs Open source LLM engineering platform for tracing and analytics Open source AI gateway for LLM request monitoring ## What You Get Integrating a third-party observability platform provides: * **Application Performance Monitoring (APM)**: Trace requests across services * **Log aggregation**: Centralized logging with search and filtering * **Custom metrics**: Track business and application-specific metrics * **Alerting**: Get notified when metrics exceed thresholds * **Dashboards**: Visualize system health and performance trends ## Comparison with Built-in Observability | Feature | Porter Built-in | Third-party Platforms | | ---------------------- | --------------- | ----------------------------- | | Pod status and metrics | ✅ | ✅ | | Node metrics | ✅ | ✅ | | Application logs | Basic | Advanced search and filtering | | Distributed tracing | ❌ | ✅ | | Custom dashboards | ❌ | ✅ | | Alerting | ❌ | ✅ | | Long-term retention | Limited | Configurable | For production workloads, we recommend integrating at least one third-party observability platform to get comprehensive visibility into your applications. # Deploying multiple apps with porter.yaml Source: https://docs.porter.run/applications/configuration-as-code/addons-porter-yaml Define and deploy multiple Porter applications alongside datastores and custom Helm chart addons in a single porter.yaml file using porter apply You can deploy multiple Porter apps alongside multiple Porter addons in a single `porter.yaml` file. Currently only in-cluster datastores are supported for now. Requires Porter CLI **v0.68.30 or later**. Check your version with `porter version`, and see the [CLI installation guide](https://docs.porter.run/cli/installation) to install or upgrade. ```bash theme={null} porter apply -f porter.yaml --wait ``` To guarantee that the datastore and its env group are ready before the app starts, use the --wait flag. Addons are always applied before apps, and `--wait` holds the apply until each datastore is fully available (up to 10 minutes) before any apps deploy. This guarantees that the datastores' environment groups and credentials exist by the time the apps start. The flag also waits for each app's rollout to succeed before exiting. Without `--wait`, the CLI proceeds about 10 seconds after creating a datastore. Apps may then fail their first deploy with a missing-environment-group error until the datastore finishes provisioning. When a datastore is provisioned, Porter automatically creates an environment group with the **same name as the datastore**. This environment group holds the datastore's connection details (host, port, credentials, etc.) as environment variables. To link a datastore to an app, reference that environment group by name in the app's `envGroups` section. The entry must match the datastore's `name` exactly. For example, to connect an app to a datastore named `cache`: ```yaml theme={null} envGroups: - cache ``` Embed the app schema as an item in the `apps:` list. Then attach addons as a list of `addons:` items. ## Example Configuration ```yaml theme={null} apps: - version: v2 name: backend services: - name: server run: "" type: web instances: 1 cpuCores: 0.2 ramMegabytes: 100 terminationGracePeriodSeconds: 30 port: 80 sleep: false private: true envGroups: # Environment groups can be used to inject environment variables from the addons into the service. - cache - db image: repository: nginx tag: latest - version: v2 name: frontend services: - name: dashboard run: "" type: web instances: 1 cpuCores: 0.2 ramMegabytes: 100 terminationGracePeriodSeconds: 30 port: 80 sleep: false private: true envGroups: - cache - db image: repository: nginx tag: latest - version: v2 name: api services: - name: worker run: "" type: worker instances: 1 cpuCores: 0.2 ramMegabytes: 100 terminationGracePeriodSeconds: 30 port: 80 sleep: false envGroups: - cache - db image: repository: nginx tag: latest addons: - name: cache type: redis kind: in-cluster config: storageGigabytes: 2 # Persistent storage size in GB. Cannot be changed after creation. cpuCores: 0.1 ramMegabytes: 110 - name: db type: postgres kind: in-cluster config: storageGigabytes: 2 cpuCores: 0.1 ramMegabytes: 110 ``` # Configuration as code with porter.yaml Source: https://docs.porter.run/applications/configuration-as-code/overview Define application services, builds, and deployment settings in a porter.yaml file for CI/CD pipelines and version-controlled infrastructure ## What is `porter.yaml`? A `porter.yaml` file (or files, if you choose to set up multiple within your project/repository) defines your application's services, build configuration, and deployment settings as code. This file is used with the `porter apply` CLI command to deploy and update applications, enabling version-controlled infrastructure and automated CI/CD pipelines. ## When to Use Configuration-as-Code ### CI/CD Pipelines Deploy your application automatically on every push using `porter apply`: ```bash theme={null} porter apply -f porter.yaml ``` ### Version-Controlled Infrastructure Track infrastructure changes alongside your code. Every deployment configuration change goes through code review. ### Preview Environments Spin up isolated environments for pull requests with consistent configuration: ```bash theme={null} porter apply -f porter.yaml --preview ``` ## How It Works Create a `porter.yaml` file in your repository that describes your application's services, resources, and settings. The Porter CLI reads your configuration and sends it to the Porter API. If a `build` section is defined, Porter builds and pushes your container image. Porter deploys or updates your services according to the configuration. ## Getting Started ### 1. Export Existing Configuration If you already have an app deployed on Porter, export its current configuration: ```bash theme={null} porter app yaml my-app > porter.yaml ``` ### 2. Create from Scratch Start with a minimal configuration: ```yaml theme={null} version: v2 name: my-app services: - name: web type: web run: npm start port: 3000 cpuCores: 0.5 ramMegabytes: 512 build: method: docker context: . dockerfile: ./Dockerfile ``` ### 3. Deploy Apply the configuration to deploy your app: ```bash theme={null} porter apply -f porter.yaml ``` ### Example `porter.yaml` The following is an example of a v2 `porter.yaml` file, which is the latest version of the spec. This example covers many of the available fields, but not all of them. For a full list of configurable options, see the [full reference](/applications/configuration-as-code/reference). ```yaml theme={null} version: v2 name: my-app services: - name: api type: web run: node index.js port: 8080 cpuCores: 0.1 ramMegabytes: 256 autoscaling: enabled: true minInstances: 1 maxInstances: 3 memoryThresholdPercent: 60 cpuThresholdPercent: 60 private: false domains: - name: test1.example.com healthCheck: enabled: true httpPath: /healthz - name: example-wkr type: worker run: echo 'work' port: 8081 cpuCores: 0.1 ramMegabytes: 256 instances: 1 - name: example-job type: job run: echo 'hello world' allowConcurrent: true cpuCores: 0.1 ramMegabytes: 256 cron: '*/10 * * * *' predeploy: run: ls build: method: docker context: ./ dockerfile: ./app/Dockerfile env: NODE_ENV: production envGroups: - production-env-group ``` For detailed configuration options for each service type, see: * [Web Service](/applications/configuration-as-code/services/web-service) * [Worker Service](/applications/configuration-as-code/services/worker-service) * [Job Service](/applications/configuration-as-code/services/job-service) ## Configuration vs Dashboard When you deploy using `porter apply`, the configuration in `porter.yaml` takes precedence. Changes made in the Porter dashboard may be overwritten on the next deployment. For consistent deployments, we recommend: * Use `porter.yaml` as the source of truth for production * Use the dashboard for experimentation and one-off changes * Export dashboard changes with `porter app yaml` to update your configuration file ## Next Steps * [Full Reference](/applications/configuration-as-code/reference) - Complete documentation of all configuration options * [porter apply Command](/standard/cli/command-reference/porter-apply) - CLI reference for deployments * [Using Other CI Tools](/applications/deploy/using-other-ci-tools) - Integrate with GitHub Actions, GitLab CI, etc. # porter.yaml reference Source: https://docs.porter.run/applications/configuration-as-code/reference Complete field reference for porter.yaml including build, image, services, environment variables, predeploy, and autoscaling options This is the complete reference for all fields that can be set in a `porter.yaml` file. ## Top-Level Fields | Field | Type | Required | Description | | --------------- | --------- | ----------- | ---------------------------------------------------------- | | `version` | string | Yes | Must be `v2` | | `name` | string | Conditional | App name. Required unless `PORTER_APP_NAME` env var is set | | `build` | object | Conditional | Build configuration. Cannot be used with `image` | | `image` | object | Conditional | Pre-built image configuration. Cannot be used with `build` | | `services` | array | Yes | List of service definitions | | `env` | object | No | Environment variables | | `envGroups` | string\[] | No | Names of environment groups to attach | | `predeploy` | object | No | Pre-deploy job configuration | | `initialDeploy` | object | No | Job to run only on first deployment | | `autoRollback` | object | No | Automatic rollback settings | | `efsStorage` | object | No | AWS EFS storage configuration | You must specify either `build` or `image`, but not both. Use `build` when Porter should build your container image, or `image` when using a pre-built image from a registry. *** ## `version` `string` Required The schema version. Must be `v2`. ```yaml theme={null} version: v2 ``` *** ## `name` `string` Required (unless `PORTER_APP_NAME` is set) The application name. Must be 31 characters or less, consist of lowercase alphanumeric characters or `-`, and start and end with an alphanumeric character. ```yaml theme={null} name: my-app ``` *** ## `build` `object` Optional Configuration for building container images. Cannot be used together with `image`. | Field | Type | Required | Description | | ------------ | --------- | ----------- | ------------------------------------------------ | | `method` | string | Yes | Build method: `docker` or `pack` | | `context` | string | Yes | Build context directory | | `dockerfile` | string | Conditional | Dockerfile path (required if method is `docker`) | | `builder` | string | Conditional | Builder image (required if method is `pack`) | | `buildpacks` | string\[] | No | List of buildpacks (for `pack` method) | ```yaml Docker Build theme={null} build: method: docker context: . dockerfile: ./Dockerfile ``` ```yaml Buildpack Build theme={null} build: method: pack context: . builder: heroku/buildpacks:20 buildpacks: - heroku/python - heroku/nodejs ``` We recommend defining a Dockerfile over using buildpacks - if you're not sure which to use, default to creating a Dockerfile for your application. For more information about creating a Dockerfile, you can look [here](https://docs.docker.com/get-started/docker-concepts/building-images/writing-a-dockerfile/). For available `builder` and `buildpacks` values, see the [Cloud Native Buildpacks Registry](https://registry.buildpacks.io/) or [Paketo Builders Reference](https://paketo.io/docs/reference/builders-reference/) documentation. Common builders include `heroku/builder:24` and `paketobuildpacks/builder-jammy-full:latest`. *** ## `image` `object` Optional Configuration for using a pre-built container image. Cannot be used together with `build`. | Field | Type | Required | Description | | ------------ | ------ | -------- | ----------------------------------------------- | | `repository` | string | Yes | Image repository URL | | `tag` | string | No | Image tag (can be overridden with `--tag` flag) | ```yaml theme={null} image: repository: my-registry/my-app tag: latest ``` *** ## `env` `object` Optional Environment variables to set for all services. Values must be strings. ```yaml theme={null} env: PORT: 8080 ``` For sensitive values, use environment groups instead of hardcoding them in `porter.yaml`. *** ## `envGroups` `string[]` Optional Names of [environment groups](/applications/configure/environment-groups) to attach to the application. Environment groups are project-wide and must already exist before deploying. ```yaml theme={null} envGroups: - production-secrets - shared-config - database-credentials ``` *** ## `predeploy` `object` Optional A job that runs before deploying services. Commonly used for database migrations. | Field | Type | Required | Description | | -------------- | ------ | -------- | ------------------ | | `run` | string | Yes | Command to execute | | `cpuCores` | number | No | CPU allocation | | `ramMegabytes` | number | No | Memory allocation | ```yaml theme={null} predeploy: run: echo "predeploy" ``` See [Predeploy Configuration](/applications/configuration-as-code/services/predeploy) for more details. *** ## `initialDeploy` `object` Optional A job that runs only on the first deployment of a preview environment. Useful for one-time setup tasks like database seeding. | Field | Type | Required | Description | | -------------- | ------ | -------- | ------------------ | | `run` | string | Yes | Command to execute | | `cpuCores` | number | No | CPU allocation | | `ramMegabytes` | number | No | Memory allocation | ```yaml theme={null} initialDeploy: run: npm run seed cpuCores: 0.25 ramMegabytes: 256 ``` *** ## `autoRollback` `object` Optional Configure automatic rollback when deployments fail. | Field | Type | Required | Description | | --------- | ------- | -------- | ------------------------------- | | `enabled` | boolean | Yes | Enable or disable auto-rollback | ```yaml theme={null} autoRollback: enabled: true ``` When enabled, Porter automatically rolls back all services to the last successfully deployed version if any service fails to deploy. *** ## `efsStorage` `object` Optional Enable AWS EFS (Elastic File System) storage for persistent data. Only available on AWS clusters. | Field | Type | Required | Description | | --------- | ------- | -------- | ------------------ | | `enabled` | boolean | Yes | Enable EFS storage | ```yaml theme={null} efsStorage: enabled: true ``` *** ## `services` `array` Required List of service definitions. Each service represents a deployable unit of your application. ### Common Service Fields These fields apply to all service types: | Field | Type | Required | Description | | ------------------------------- | ------- | ----------- | ------------------------------------------------------------------------ | | `name` | string | Yes | Unique service identifier (max 31 chars, lowercase alphanumeric and `-`) | | `type` | string | Yes | Service type: `web`, `worker`, or `job` | | `run` | string | Yes | Command to execute | | `instances` | integer | No | Number of replicas (not for jobs) | | `cpuCores` | number | Yes | CPU allocation (e.g., `0.5`, `1`, `2`) | | `ramMegabytes` | integer | Yes | Memory allocation in MB | | `gpuCoresNvidia` | integer | No | NVIDIA GPU cores to allocate | | `port` | integer | Conditional | Port the service listens on (required for `web`) | | `nodeGroup` | string | No | UUID of a user node group to run on | | `connections` | array | No | External cloud service connections | | `terminationGracePeriodSeconds` | integer | No | Seconds to wait before force-killing pods | | `serviceMeshEnabled` | boolean | No | Enable service mesh for inter-service communication | | `metricsScraping` | object | No | Prometheus metrics scraping configuration | ### Service Types | Type | Description | Documentation | | -------- | ---------------------------------------------- | ------------------------------------------------------------------------------ | | `web` | HTTP services with public or private endpoints | [Web Services](/applications/configuration-as-code/services/web-service) | | `worker` | Background processing services | [Worker Services](/applications/configuration-as-code/services/worker-service) | | `job` | Scheduled or on-demand tasks | [Job Services](/applications/configuration-as-code/services/job-service) | ### Basic Example ```yaml theme={null} services: - name: web type: web run: python app.py instances: 1 cpuCores: 1 ramMegabytes: 1024 port: 8080 - name: web-on-user-node-group type: web run: python app.py instances: 1 cpuCores: 1 ramMegabytes: 1024 port: 8080 nodeGroup: 123e4567-e89b-12d3-a456-426614174000 ``` *** ## `connections` `array` Optional Configure connections to external cloud services. See [Connections Configuration](/applications/configuration-as-code/services/connections) for full documentation. ### AWS Role Connection Attach an IAM role to your service for AWS API access. ```yaml theme={null} services: - name: web ... connections: - type: awsRole role: iam-role-name ``` ### Azure Managed Identity Connection Bind a User Assigned Managed Identity to your service for Azure API access via Azure Workload Identity. ```yaml theme={null} services: - name: web ... connections: - type: azureManagedIdentity identityName: my-managed-identity resourceGroup: my-resource-group ``` ### GCP Service Account Connection Bind a GCP IAM service account to your service for GCP API access without service account key files. ```yaml theme={null} services: - name: web ... connections: - type: gcpServiceAccount serviceAccountEmail: my-app@my-project.iam.gserviceaccount.com ``` ### Cloud SQL Connection (GCP) Connect to Google Cloud SQL instances. ```yaml theme={null} services: - name: web ... connections: - type: cloudSql config: cloudSqlConnectionName: project-123456:us-east1:instance-name cloudSqlDatabasePort: 5432 cloudSqlServiceAccount: service-account-name ``` ### Persistent Disk Connection Attach persistent storage to your service. ```yaml theme={null} services: - name: web ... connections: - type: disk config: diskName: my-disk ``` *** ## `gpu` `object` Optional Configure GPU resources for machine learning workloads. | Field | Type | Description | | ---------------- | ------- | -------------------------- | | `gpuCoresNvidia` | integer | Number of NVIDIA GPU cores | ```yaml theme={null} services: - name: web ... gpuCoresNvidia: 1 nodeGroup: 123e4567-e89b-12d3-a456-426614174000 ``` GPU workloads require a node group with GPU-enabled instances. *** ## `metricsScraping` `object` Optional Configure Prometheus metrics scraping for custom application metrics. | Field | Type | Description | | ----------------------- | ------- | ----------------------------------------- | | `enabled` | boolean | Enable metrics scraping | | `path` | string | HTTP path to scrape (default: `/metrics`) | | `port` | integer | Port to scrape metrics from | | `scrapeIntervalSeconds` | integer | Scrape interval in seconds (default: 60) | ```yaml theme={null} services: - name: web ... metricsScraping: enabled: true path: /metrics port: 9090 ``` *** ## Complete Example ```yaml theme={null} version: v2 name: my-app build: method: docker context: . dockerfile: ./Dockerfile env: PORT: "8080" LOG_LEVEL: "info" envGroups: - production-secrets - shared-config predeploy: run: python manage.py migrate cpuCores: 0.5 ramMegabytes: 512 initialDeploy: run: python manage.py seed cpuCores: 0.25 ramMegabytes: 256 autoRollback: enabled: true efsStorage: enabled: true services: # Web service with all options - name: web type: web run: python app.py instances: 2 cpuCores: 1 ramMegabytes: 1024 port: 8080 terminationGracePeriodSeconds: 30 autoscaling: enabled: true minInstances: 2 maxInstances: 10 cpuThresholdPercent: 80 memoryThresholdPercent: 80 healthCheck: enabled: true httpPath: /healthz domains: - name: app.example.com metricsScraping: enabled: true path: /metrics port: 9090 scrapeIntervalSeconds: 30 connections: - type: awsRole role: my-app-role # GPU-enabled worker on a custom node group - name: ml-worker type: worker run: python ml_worker.py instances: 1 cpuCores: 4 ramMegabytes: 16384 gpuCoresNvidia: 1 nodeGroup: 123e4567-e89b-12d3-a456-426614174000 terminationGracePeriodSeconds: 60 serviceMeshEnabled: true # Background worker - name: worker type: worker run: python worker.py instances: 1 cpuCores: 0.5 ramMegabytes: 512 # Scheduled job - name: cleanup type: job run: python cleanup.py cpuCores: 0.5 ramMegabytes: 256 cron: "0 0 * * *" ``` *** ## Related Documentation * [Web Services](/applications/configuration-as-code/services/web-service) - Web service configuration * [Worker Services](/applications/configuration-as-code/services/worker-service) - Worker service configuration * [Job Services](/applications/configuration-as-code/services/job-service) - Job service configuration * [Predeploy Jobs](/applications/configuration-as-code/services/predeploy) - Pre-deploy job configuration * [Autoscaling Configuration](/applications/configuration-as-code/services/autoscaling) - Autoscaling configuration reference * [Connections Configuration](/applications/configuration-as-code/services/connections) - Cloud connections reference * [porter apply](/standard/cli/command-reference/porter-apply) - CLI reference for deployments # Autoscaling in porter.yaml Source: https://docs.porter.run/applications/configuration-as-code/services/autoscaling Configure horizontal pod autoscaling in porter.yaml with CPU and memory utilization thresholds, min/max replicas, and scaling behavior Configure horizontal pod autoscaling to automatically adjust the number of replicas based on resource utilization. ## Field Reference | Field | Type | Description | | ------------------------ | ------- | ------------------------------ | | `enabled` | boolean | Enable autoscaling | | `minInstances` | integer | Minimum number of replicas | | `maxInstances` | integer | Maximum number of replicas | | `cpuThresholdPercent` | integer | CPU usage threshold (0-100) | | `memoryThresholdPercent` | integer | Memory usage threshold (0-100) | ## Basic Configuration ```yaml theme={null} services: - name: api # ... autoscaling: enabled: true minInstances: 2 maxInstances: 10 cpuThresholdPercent: 80 memoryThresholdPercent: 80 ``` When autoscaling is enabled, the `instances` field is ignored. The autoscaler manages replica count automatically. For high availability, set `minInstances` to at least 3. See [High Availability Applications](/applications/configure/zero-downtime-deployments#high-availability-applications) for more details. ## How It Works When either CPU or memory usage exceeds your configured threshold, Porter automatically adds replicas. When usage drops, replicas are removed (down to your minimum). ### Example: Autoscaling in Action Consider an API service with this configuration: ```yaml theme={null} autoscaling: enabled: true minInstances: 2 maxInstances: 10 cpuThresholdPercent: 60 memoryThresholdPercent: 80 ``` Here's how the autoscaler responds to changing load: | Time | Avg CPU | Avg Memory | Replicas | What Happens | | ---- | ------- | ---------- | -------- | ----------------------------------------------------------- | | t=0 | 30% | 40% | 2 | Baseline: both metrics below thresholds | | t=1 | 75% | 50% | 4 | CPU (75%) exceeds 60% threshold → scale up | | t=2 | 90% | 60% | 6 | CPU still high → continue scaling up | | t=3 | 55% | 85% | 8 | CPU stabilized, but memory (85%) exceeds 80% → scale up | | t=4 | 45% | 70% | 8 | Both metrics below thresholds → no change (cooldown period) | | t=5 | 40% | 50% | 5 | Sustained low usage → scale down | | t=6 | 35% | 45% | 2 | Continue scaling down to minimum | Key behaviors: * **Either metric triggers scaling**: If CPU *or* memory exceeds its threshold, replicas are added * **Both must be low to scale down**: Replicas are only removed when both CPU and memory are below their thresholds * **Respects bounds**: Replicas never drop below `minInstances` (2) or exceed `maxInstances` (10) * **Gradual changes**: The autoscaler adjusts incrementally, not all at once, to avoid oscillation ## Custom Metrics Autoscaling (Prometheus) Scale based on application-specific metrics like queue length, request latency, or custom business metrics. | Field | Type | Description | | ---------------------------------------------------------------- | ------ | ------------------------------------------------------- | | `customAutoscaling.prometheusMetricCustomAutoscaling.metricName` | string | Prometheus metric name | | `customAutoscaling.prometheusMetricCustomAutoscaling.threshold` | number | Threshold value to trigger scaling | | `customAutoscaling.prometheusMetricCustomAutoscaling.query` | string | Custom PromQL query (optional, defaults to metric name) | ```yaml theme={null} services: - name: api # ... autoscaling: enabled: true minInstances: 1 maxInstances: 10 customAutoscaling: prometheusMetricCustomAutoscaling: metricName: "http_requests_per_second" threshold: 100 query: "rate(http_requests_total[5m])" ``` Custom metrics autoscaling requires Prometheus to be accessible in your cluster. See [Custom Metrics and Autoscaling](/applications/observability/custom-metrics-and-autoscaling) for setup details. ## Temporal Autoscaling Scale Temporal workflow workers based on task queue depth. Porter monitors your Temporal task queues and automatically adjusts worker count. Temporal autoscaling requires a Temporal integration to be configured. See [Temporal Autoscaling](/applications/configure/temporal-autoscaling) for setup details. | Field | Type | Description | | ------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------ | | `temporalAutoscaling.temporalIntegrationId` | string | UUID of the Temporal integration | | `temporalAutoscaling.taskQueue` | string | Name of the Temporal task queue to monitor | | `temporalAutoscaling.targetQueueSize` | integer | How many queued tasks each replica should handle (e.g., set to 10 with 100 tasks queued → 10 replicas) | ```yaml theme={null} services: - name: temporal-worker # ... autoscaling: enabled: true minInstances: 2 maxInstances: 50 temporalAutoscaling: temporalIntegrationId: "550e8400-e29b-41d4-a716-446655440000" taskQueue: "my-task-queue" targetQueueSize: 10 ``` ## Related Documentation * [Autoscaling Overview](/applications/configure/autoscaling) - UI-based configuration and concepts * [Web Services](/applications/configuration-as-code/services/web-service) - Web service configuration * [Worker Services](/applications/configuration-as-code/services/worker-service) - Worker service configuration # Connections in porter.yaml Source: https://docs.porter.run/applications/configuration-as-code/services/connections Connect Porter services to AWS IAM roles, Azure managed identities, Google Cloud SQL instances, and persistent disks using porter.yaml connections. Connect your services to external cloud resources like AWS IAM roles, Azure managed identities, Google Cloud SQL instances, and persistent disks. ## Connection Types | Type | Description | Cloud Provider | | ---------------------- | ---------------------------------------------------------- | -------------- | | `awsRole` | Attach an IAM role for AWS API access | AWS | | `azureManagedIdentity` | Bind a User Assigned Managed Identity for Azure API access | Azure | | `gcpServiceAccount` | Bind a GCP IAM service account for GCP API access | GCP | | `cloudSql` | Connect to Google Cloud SQL instances | GCP | | `disk` | Attach persistent storage | All | *** ## AWS Role Connection Attach an IAM role to your service for secure AWS API access without managing credentials. ### Field Reference | Field | Type | Required | Description | | ------ | ------ | -------- | ----------------- | | `type` | string | Yes | Must be `awsRole` | | `role` | string | Yes | IAM role name | ### Example ```yaml theme={null} services: - name: api # ... connections: - type: awsRole role: my-app-s3-access ``` *** ## Azure Managed Identity Connection Bind a User Assigned Managed Identity (UAMI) to your service for secure Azure API access without managing credentials. Porter uses [Azure Workload Identity](https://learn.microsoft.com/en-us/azure/aks/workload-identity-overview) to federate the service's Kubernetes service account with the UAMI, so your application can authenticate to Azure resources using `DefaultAzureCredential` (or any credential type that supports workload identity). This feature is only available on AKS clusters created through Porter and must be enabled at the project level. Reach out to Porter support if you don't see it available on your project. ### Prerequisites Before adding this connection to your service, you must: 1. Have a User Assigned Managed Identity provisioned in your Azure subscription. Porter does **not** create the UAMI for you — provision it via the Azure Portal, Terraform, or the Azure CLI. 2. Grant the UAMI the Azure RBAC role assignments it needs to access the resources your service will call (e.g. `Storage Blob Data Reader` on a storage account). When your service deploys, Porter creates a [federated identity credential](https://learn.microsoft.com/en-us/azure/active-directory/develop/workload-identity-federation) on the UAMI that maps your service's Kubernetes service account to the identity. At runtime, the pod receives a projected OIDC token that Azure exchanges for an access token scoped to the UAMI. ### Field Reference | Field | Type | Required | Description | | --------------- | ------ | -------- | ---------------------------------------------------- | | `type` | string | Yes | Must be `azureManagedIdentity` | | `identityName` | string | Yes | Name of the User Assigned Managed Identity | | `resourceGroup` | string | Yes | Azure resource group containing the managed identity | ### Example ```yaml theme={null} services: - name: api # ... connections: - type: azureManagedIdentity identityName: my-app-identity resourceGroup: my-resource-group ``` This connection grants your service every permission assigned to the UAMI in Azure. Scope role assignments narrowly — a UAMI with subscription-level Owner is rarely what you want. Only one `azureManagedIdentity` connection is permitted per service. If you need to access resources across multiple identities, consolidate role assignments onto a single UAMI. *** ## GCP Service Account Connection Bind a GCP IAM service account to your service for secure GCP API access without managing service account key files. Your application can authenticate to GCP resources using [Application Default Credentials](https://cloud.google.com/docs/authentication/application-default-credentials), and Porter handles the identity binding for your service. ### Prerequisites Before adding this connection to your service, you must: 1. Have a GCP IAM service account provisioned in the same GCP project as your Porter infrastructure. Porter does **not** create the IAM service account for you — provision it via the Google Cloud Console, Terraform, or the `gcloud` CLI. 2. Grant the IAM service account the GCP IAM roles it needs to access the resources your service will call (e.g. `roles/storage.objectViewer` on a storage bucket). When your service deploys, Porter configures the required IAM binding so the service can receive short-lived credentials scoped to the IAM service account. ### Field Reference | Field | Type | Required | Description | | --------------------- | ------ | -------- | ------------------------------------------------------------------- | | `type` | string | Yes | Must be `gcpServiceAccount` | | `serviceAccountEmail` | string | Yes | Fully-qualified email of the GCP IAM service account to impersonate | ### Example ```yaml theme={null} services: - name: api # ... connections: - type: gcpServiceAccount serviceAccountEmail: my-app@my-project.iam.gserviceaccount.com ``` This connection grants your service every permission assigned to the IAM service account in GCP. Scope IAM role bindings narrowly — a service account with project-level `roles/owner` is rarely what you want. Only one `gcpServiceAccount` connection is permitted per service. If you need to access resources across multiple service accounts, consolidate IAM role bindings onto a single service account. *** ## Cloud SQL Connection (GCP) Connect to Google Cloud SQL instances using the Cloud SQL Auth Proxy for secure database access. Your GCP Service account must be configured in the Connections tab of your cluster settings before it can be used in `porter.yaml`. ### Field Reference | Field | Type | Required | Description | | ------------------------------- | ------- | -------- | ----------------------------------------- | | `type` | string | Yes | Must be `cloudSql` | | `config.cloudSqlConnectionName` | string | Yes | Cloud SQL instance connection name | | `config.cloudSqlDatabasePort` | integer | Yes | Database port (e.g., 5432 for PostgreSQL) | | `config.cloudSqlServiceAccount` | string | Yes | GCP service account name | ### Example ```yaml theme={null} services: - name: api # ... connections: - type: cloudSql config: cloudSqlConnectionName: my-project-123456:us-east1:my-instance cloudSqlDatabasePort: 5432 cloudSqlServiceAccount: my-service-account ``` The connection name follows the format `project-id:region:instance-name`. You can find this in the Google Cloud Console under your Cloud SQL instance details. *** ## Persistent Disk Connection Attach persistent storage to your service for data that needs to survive pod restarts. Your persistent disk must be created in the Add-Ons tab of Porter before it can be used in `porter.yaml`. ### Field Reference | Field | Type | Required | Description | | ----------------- | ------ | -------- | --------------------------- | | `type` | string | Yes | Must be `disk` | | `config.diskName` | string | Yes | Name of the persistent disk | ### Example ```yaml theme={null} services: - name: api # ... connections: - type: disk config: diskName: my-persistent-data ``` Persistent disks are tied to specific availability zones. Services using persistent disks cannot be scheduled across multiple zones. *** ## Multiple Connections You can attach multiple connections to a single service (but only one of each type of connection): ```yaml theme={null} services: - name: api # ... connections: - type: awsRole role: api-s3-access - type: disk config: diskName: cache-storage ``` *** ## Related Documentation * [Web Services](/applications/configuration-as-code/services/web-service) - Web service configuration * [Worker Services](/applications/configuration-as-code/services/worker-service) - Worker service configuration * [Job Services](/applications/configuration-as-code/services/job-service) - Job service configuration * [porter.yaml Reference](/applications/configuration-as-code/reference) - Complete configuration reference # Job services in porter.yaml Source: https://docs.porter.run/applications/configuration-as-code/services/job-service Complete field reference for job services in porter.yaml including cron schedules, timeout settings, concurrency controls, and resource limits Job services are for scheduled or on-demand tasks that run to completion. They're ideal for cron jobs, data processing, and one-time tasks. This is a complete reference for all fields that can be set for a job service in `porter.yaml`. ## Field Reference | Field | Type | Required | Description | | ------------------------------- | ------- | -------- | --------------------------------- | | `name` | string | Yes | Service identifier (max 31 chars) | | `type` | string | Yes | Must be `job` | | `run` | string | Yes | Command to execute | | `cpuCores` | number | Yes | CPU allocation | | `ramMegabytes` | integer | Yes | Memory allocation in MB | | `cron` | string | No | Cron schedule expression | | `suspendCron` | boolean | No | Temporarily disable cron schedule | | `allowConcurrent` | boolean | No | Allow concurrent job runs | | `timeoutSeconds` | integer | No | Maximum job duration | | `connections` | array | No | External cloud connections | | `terminationGracePeriodSeconds` | integer | No | Graceful shutdown timeout | | `gpuCoresNvidia` | integer | No | NVIDIA GPU cores | | `nodeGroup` | string | No | Node group UUID | *** ## Basic Example ```yaml theme={null} services: - name: cleanup type: job run: npm run cleanup cpuCores: 0.25 ramMegabytes: 256 cron: "0 0 * * *" ``` *** ## `cron` `string` Optional A cron expression that defines when the job should run. Uses [standard 5-field cron syntax](https://en.wikipedia.org/wiki/Cron). ```yaml theme={null} cron: "0 0 * * *" ``` If no cron expression is provided, the job will default to run every 5 minutes. *** ## `suspendCron` `boolean` Optional Disable the cron schedule without removing it. The job won't run on schedule but can still be triggered manually. ```yaml theme={null} suspendCron: true ``` Use this to pause scheduled jobs during maintenance windows or while debugging, or to create one-off jobs. *** ## `allowConcurrent` `boolean` Optional Allow multiple instances of the job to run simultaneously. By default, a new job run won't start if a previous run is still in progress. ```yaml theme={null} allowConcurrent: true ``` Be careful enabling this for jobs that modify shared resources. Concurrent runs may cause race conditions or data inconsistency. *** ## `timeoutSeconds` `integer` Optional Maximum number of seconds the job is allowed to run before being terminated. ```yaml theme={null} timeoutSeconds: 3600 ``` If not specified, jobs may run indefinitely. It's recommended to set a reasonable timeout for all jobs. *** ## `connections` `array` Optional Connect to external cloud services. See [Connections Configuration](/applications/configuration-as-code/services/connections) for full documentation. *** ## `terminationGracePeriodSeconds` `integer` Optional Seconds to wait for graceful shutdown before forcefully terminating the job. ```yaml theme={null} terminationGracePeriodSeconds: 30 ``` Use this to ensure jobs can clean up resources or checkpoint progress before termination. *** ## `gpuCoresNvidia` `integer` Optional Allocate NVIDIA GPU cores for ML training, inference, or GPU-accelerated processing. ```yaml theme={null} gpuCoresNvidia: 1 nodeGroup: gpu-node-group-uuid ``` Requires a node group with GPU-enabled instances. *** ## Triggering Jobs Manually Jobs can be triggered manually using the Porter CLI: ```bash theme={null} # Trigger a job run porter app run my-app --job my-job # Trigger and wait for completion porter app run my-app --job my-job --wait # Override concurrent restriction porter app run my-app --job my-job --allow-concurrent ``` *** ## Complete Example ```yaml theme={null} services: - name: daily-report type: job run: python generate_report.py cpuCores: 1 ramMegabytes: 2048 # Schedule: Daily at 6 AM UTC cron: "0 6 * * *" # Allow up to 2 hours timeoutSeconds: 7200 # Don't allow concurrent runs allowConcurrent: false # Cloud connections connections: - type: awsRole role: report-s3-access # Graceful shutdown terminationGracePeriodSeconds: 60 ``` *** ## Common Use Cases ### Database Cleanup ```yaml theme={null} services: - name: db-cleanup type: job run: npm run cleanup-old-records cpuCores: 0.25 ramMegabytes: 256 cron: "0 3 * * *" # 3 AM daily timeoutSeconds: 1800 allowConcurrent: false ``` ### Data Export ```yaml theme={null} services: - name: weekly-export type: job run: python export_data.py cpuCores: 0.5 ramMegabytes: 1024 cron: "0 0 * * 0" # Sunday at midnight timeoutSeconds: 14400 # 4 hours connections: - type: awsRole role: s3-export-role ``` ### ML Training Job ```yaml theme={null} services: - name: model-training type: job run: python train.py cpuCores: 8 ramMegabytes: 32768 gpuCoresNvidia: 4 nodeGroup: gpu-node-group-uuid timeoutSeconds: 86400 # 24 hours terminationGracePeriodSeconds: 300 ``` ### Health Check / Monitoring ```yaml theme={null} services: - name: healthcheck type: job run: ./check_dependencies.sh cpuCores: 0.1 ramMegabytes: 128 cron: "*/5 * * * *" # Every 5 minutes timeoutSeconds: 60 allowConcurrent: false ``` ### Manual Migration Job ```yaml theme={null} services: - name: data-migration type: job run: python migrate.py cpuCores: 2 ramMegabytes: 4096 suspendCron: true # otherwise, the job will run every 5 minutes by default timeoutSeconds: 28800 # 8 hours allowConcurrent: false terminationGracePeriodSeconds: 120 ``` Jobs `suspendCront: true` must be triggered manually using `porter app run --job`. # Predeploy in porter.yaml Source: https://docs.porter.run/applications/configuration-as-code/services/predeploy Configure predeploy jobs in porter.yaml to run database migrations or setup tasks after each build completes but before deployment begins Predeploy is run following a new build, but before the build is deployed to the cluster. This is a good place to run migrations, or to generate any files that are needed for the deploy. For more information on pre-deploy jobs in Porter, see [Pre-deploy Jobs](/applications/deploy/pre-deploy-jobs). ```yaml theme={null} predeploy: run: 'npm run migrate' ``` # Web services in porter.yaml Source: https://docs.porter.run/applications/configuration-as-code/services/web-service Complete field reference for web services in porter.yaml including ports, custom domains, health checks, autoscaling, and path-based routing Web services are HTTP-based services that can be exposed publicly or kept private within your cluster. This is a complete reference for all fields that can be set for a web service in `porter.yaml`. ## Field Reference | Field | Type | Required | Description | | ------------------------------- | ------- | -------- | ------------------------------------------------------------------------------ | | `name` | string | Yes | Service identifier (max 31 chars) | | `type` | string | Yes | Must be `web` | | `run` | string | Yes | Command to execute | | `port` | integer | Yes | Port the service listens on | | `cpuCores` | number | Yes | CPU allocation | | `ramMegabytes` | integer | Yes | Memory allocation in MB | | `instances` | integer | No | Number of replicas (default: 1) | | `private` | boolean | No | Route the service through the cluster's private load balancer (default: false) | | `loadBalancerConfig` | object | No | Load balancer configuration (`public_lb` or `private_lb`) | | `disableTLS` | boolean | No | Disable TLS termination | | `autoscaling` | object | No | Autoscaling configuration | | `domains` | array | No | Custom domain configuration | | `healthCheck` | object | No | Combined health check config | | `livenessCheck` | object | No | Liveness probe config | | `readinessCheck` | object | No | Readiness probe config | | `startupCheck` | object | No | Startup probe config | | `pathRouting` | array | No | Path-based routing rules | | `pathRoutingConfig` | object | No | Path routing options | | `ingressAnnotations` | object | No | Custom ingress annotations | | `connections` | array | No | External cloud connections | | `serviceMeshEnabled` | boolean | No | Enable service mesh | | `metricsScraping` | object | No | Prometheus metrics config | | `terminationGracePeriodSeconds` | integer | No | Graceful shutdown timeout | | `gpuCoresNvidia` | integer | No | NVIDIA GPU cores | | `nodeGroup` | string | No | Node group UUID | *** ## Basic Example ```yaml theme={null} services: - name: api type: web run: node server.js port: 8080 cpuCores: 0.5 ramMegabytes: 512 instances: 2 ``` *** ## `private` `boolean` Optional When `true`, the service is fronted by the cluster's **private load balancer** instead of the default public one. The service is reachable only from networks peered to your VPC (PrivateLink, VPC peering, transit gateway) — not from the public internet. This requires the cluster to have a private load balancer provisioned. See [advanced cluster settings](/cloud-accounts/advanced-cluster-settings#private-load-balancer) to enable one. ```yaml theme={null} private: true ``` `private` is shorthand for `loadBalancerConfig.mode: private_lb`. Prefer `loadBalancerConfig` for new services — setting both on the same service is not supported. *** ## `loadBalancerConfig` `object` Optional Configures the load balancer that fronts the web service. Use this to explicitly route the service through the cluster's public or private load balancer. | Field | Type | Required | Description | | ------ | ------ | -------- | ------------------------------------- | | `mode` | string | Yes | `public_lb` (default) or `private_lb` | When `mode` is `private_lb`, the service is fronted by the cluster's private load balancer and is reachable only from peered networks. The cluster must have a private load balancer provisioned (see [advanced cluster settings](/cloud-accounts/advanced-cluster-settings#private-load-balancer)). Custom domains attached to a private service must have DNS provider credentials configured on the cluster so Porter can provision certificates for custom domains. When `mode` is `public_lb` or `loadBalancerConfig` is omitted, the service is fronted by the cluster's default public load balancer. ```yaml theme={null} services: - name: internal-admin type: web run: node server.js port: 8080 cpuCores: 0.5 ramMegabytes: 512 loadBalancerConfig: mode: private_lb domains: - name: admin.internal.example.com ``` *** ## `disableTLS` `boolean` Optional Disable TLS termination at the load balancer. Only use this for services that handle their own TLS or for internal testing. ```yaml theme={null} disableTLS: true ``` Disabling TLS exposes your service over HTTP. Only use this when you have a specific requirement. *** ## `autoscaling` `object` Optional Configure horizontal pod autoscaling based on CPU and memory utilization. See [Autoscaling Configuration](/applications/configuration-as-code/services/autoscaling) for full documentation. *** ## `domains` `array` Optional Configure custom domains for your web service. | Field | Type | Description | | ------ | ------ | ----------- | | `name` | string | Domain name | ```yaml theme={null} domains: - name: example.com ``` *** ## `healthCheck` `object` Optional Configure a combined health check that applies to liveness, readiness, and startup probes. | Field | Type | Description | | --------------------- | ------- | -------------------------------------- | | `enabled` | boolean | Enable health checks | | `httpPath` | string | HTTP endpoint to check | | `timeoutSeconds` | integer | Request timeout (min: 1) | | `initialDelaySeconds` | integer | Initial delay before checking (min: 0) | ```yaml theme={null} healthCheck: enabled: true httpPath: /healthz timeoutSeconds: 1 initialDelaySeconds: 15 ``` Cannot be used together with `livenessCheck`, `readinessCheck`, or `startupCheck`. Use either the combined `healthCheck` or the individual checks. For best practices on combining health checks with graceful shutdown for zero-downtime deployments, see [Zero-Downtime Deployments](/applications/configure/zero-downtime-deployments). *** ## Advanced Health Checks For fine-grained control, configure liveness, readiness, and startup probes separately. ### `livenessCheck` `object` Optional Determines if the container should be restarted. ```yaml theme={null} livenessCheck: enabled: true httpPath: /livez timeoutSeconds: 1 initialDelaySeconds: 15 ``` ### `readinessCheck` `object` Optional Determines if the container is ready to receive traffic. ```yaml theme={null} readinessCheck: enabled: true httpPath: /readyz timeoutSeconds: 1 initialDelaySeconds: 15 ``` ### `startupCheck` `object` Optional Used for slow-starting containers. Other probes are disabled until this passes. ```yaml theme={null} startupCheck: enabled: true httpPath: /startupz timeoutSeconds: 1 initialDelaySeconds: 15 ``` *** ## `pathRouting` `array` Optional Configure path-based routing to direct requests to different ports or services. | Field | Type | Required | Description | | ------------- | ------- | -------- | ------------------------------------------------ | | `path` | string | Yes | URL path prefix | | `port` | integer | Yes | Port to route to | | `serviceName` | string | No | Service to route to (defaults to current) | | `appName` | string | No | Application to route to (requires `serviceName`) | ```yaml theme={null} pathRouting: - path: /api/v1/ port: 8080 - path: /api/v2/ port: 8081 - path: /admin/ port: 9000 serviceName: admin-service - path: /auth/ port: 8080 appName: auth-app serviceName: auth-service ``` A path must be specified for the default port set in `services.port`. *** ## `pathRoutingConfig` `object` Optional Configure path rewriting behavior for path-based routing. | Field | Type | Description | | ------------- | ------ | ----------------- | | `rewriteMode` | string | Path rewrite mode | **Rewrite Modes:** | Mode | Description | Example: `/api/v1/users` | | ---------------- | ------------------------------------- | ------------------------ | | `rewrite-all` | Rewrite entire path to root (default) | `/` | | `rewrite-prefix` | Remove the matched prefix only | `/users` | | `rewrite-off` | No rewriting, keep original path | `/api/v1/users` | ```yaml theme={null} pathRouting: - path: /api/v1/ port: 8080 - path: /api/v2/ port: 8081 pathRoutingConfig: rewriteMode: rewrite-prefix ``` *** ## `ingressAnnotations` `object` Optional Add custom NGINX ingress annotations for advanced configuration. ```yaml theme={null} ingressAnnotations: nginx.ingress.kubernetes.io/proxy-connect-timeout: "18000" ``` Common use cases include increasing upload limits, configuring timeouts, and enabling WebSocket support. *** ## `connections` `array` Optional Connect to external cloud services. See [Connections Configuration](/applications/configuration-as-code/services/connections) for full documentation. *** ## `serviceMeshEnabled` `boolean` Optional Enable service mesh for enhanced inter-service communication with improved performance, reliability, and monitoring. ```yaml theme={null} serviceMeshEnabled: true ``` Recommended for applications with multiple services that communicate with each other, especially those using gRPC or WebSockets. *** ## `metricsScraping` `object` Optional Configure Prometheus metrics scraping for custom application metrics. | Field | Type | Description | | ----------------------- | ------- | ----------------------------------------- | | `enabled` | boolean | Enable metrics scraping | | `path` | string | HTTP path to scrape (default: `/metrics`) | | `port` | integer | Port to scrape metrics from | | `scrapeIntervalSeconds` | integer | Scrape interval in seconds (default: 60) | ```yaml theme={null} metricsScraping: enabled: true path: /metrics port: 9090 scrapeIntervalSeconds: 60 ``` *** ## `terminationGracePeriodSeconds` `integer` Optional Seconds to wait for graceful shutdown before forcefully terminating the container. ```yaml theme={null} terminationGracePeriodSeconds: 60 ``` Increase this value for services that need time to complete in-flight requests or cleanup tasks. *** ## `gpuCoresNvidia` `integer` Optional Allocate NVIDIA GPU cores for ML inference or GPU-accelerated workloads. ```yaml theme={null} gpuCoresNvidia: 1 nodeGroup: gpu-node-group-uuid ``` Requires a node group with GPU-enabled instances. *** ## Complete Example ```yaml theme={null} services: - name: api type: web run: npm start port: 8080 cpuCores: 1 ramMegabytes: 1024 # Autoscaling autoscaling: enabled: true minInstances: 1 maxInstances: 10 cpuThresholdPercent: 80 memoryThresholdPercent: 80 # Custom domains domains: - name: example.com # Health checks livenessCheck: enabled: true httpPath: /livez timeoutSeconds: 1 initialDelaySeconds: 15 readinessCheck: enabled: true httpPath: /readyz timeoutSeconds: 1 initialDelaySeconds: 15 # Path routing pathRouting: - path: /api/v1/ port: 8080 - path: /api/v2/ port: 8081 pathRoutingConfig: rewriteMode: rewrite-prefix # Ingress configuration ingressAnnotations: nginx.ingress.kubernetes.io/proxy-connect-timeout: "18000" # Service mesh and metrics serviceMeshEnabled: true metricsScraping: enabled: true path: /metrics port: 9090 scrapeIntervalSeconds: 60 # Cloud connections connections: - type: awsRole role: api-s3-access # Graceful shutdown terminationGracePeriodSeconds: 30 ``` # Worker services in porter.yaml Source: https://docs.porter.run/applications/configuration-as-code/services/worker-service Complete field reference for worker services in porter.yaml including health checks, autoscaling, GPU allocation, and resource limits Worker services are background processing services that don't expose HTTP endpoints. They're ideal for queue consumers, background jobs, and long-running processes. This is a complete reference for all fields that can be set for a worker service in `porter.yaml`. ## Field Reference | Field | Type | Required | Description | | ------------------------------- | ------- | -------- | --------------------------------- | | `name` | string | Yes | Service identifier (max 31 chars) | | `type` | string | Yes | Must be `worker` | | `run` | string | Yes | Command to execute | | `cpuCores` | number | Yes | CPU allocation | | `ramMegabytes` | integer | Yes | Memory allocation in MB | | `instances` | integer | No | Number of replicas (default: 1) | | `autoscaling` | object | No | Autoscaling configuration | | `healthCheck` | object | No | Combined health check config | | `livenessCheck` | object | No | Liveness probe config | | `readinessCheck` | object | No | Readiness probe config | | `startupCheck` | object | No | Startup probe config | | `connections` | array | No | External cloud connections | | `serviceMeshEnabled` | boolean | No | Enable service mesh | | `terminationGracePeriodSeconds` | integer | No | Graceful shutdown timeout | | `gpuCoresNvidia` | integer | No | NVIDIA GPU cores | | `nodeGroup` | string | No | Node group UUID | *** ## Basic Example ```yaml theme={null} services: - name: queue-worker type: worker run: npm run worker cpuCores: 0.5 ramMegabytes: 512 instances: 3 ``` *** ## `autoscaling` `object` Optional Configure horizontal pod autoscaling based on CPU and memory utilization. See [Autoscaling Configuration](/applications/configuration-as-code/services/autoscaling) for full documentation. *** ## `healthCheck` `object` Optional Configure a combined health check that applies to liveness, readiness, and startup probes. Worker services use command-based health checks since they don't expose HTTP endpoints. | Field | Type | Description | | --------------------- | ------- | -------------------------------------- | | `enabled` | boolean | Enable health checks | | `command` | string | Command to run for health check | | `timeoutSeconds` | integer | Command timeout (min: 1) | | `initialDelaySeconds` | integer | Initial delay before checking (min: 0) | ```yaml theme={null} healthCheck: enabled: true command: ./healthcheck.sh timeoutSeconds: 5 initialDelaySeconds: 15 ``` Cannot be used together with `livenessCheck`, `readinessCheck`, or `startupCheck`. Use either the combined `healthCheck` or the individual checks. For best practices on combining command-based health checks with graceful shutdown (SIGTERM handling, shutdown markers), see [Zero-Downtime Deployments: Workers](/applications/configure/zero-downtime-deployments#workers). *** ## Advanced Health Checks For fine-grained control, configure liveness, readiness, and startup probes separately. ### `livenessCheck` `object` Optional Determines if the container should be restarted. ```yaml theme={null} livenessCheck: enabled: true command: ./livez.sh timeoutSeconds: 5 initialDelaySeconds: 15 ``` ### `readinessCheck` `object` Optional Determines if the container is ready to receive work. ```yaml theme={null} readinessCheck: enabled: true command: ./readyz.sh timeoutSeconds: 5 initialDelaySeconds: 5 ``` ### `startupCheck` `object` Optional Used for slow-starting containers. Other probes are disabled until this passes. ```yaml theme={null} startupCheck: enabled: true command: ./startupz.sh timeoutSeconds: 10 initialDelaySeconds: 0 ``` *** ## `connections` `array` Optional Connect to external cloud services. See [Connections Configuration](/applications/configuration-as-code/services/connections) for full documentation. *** ## `serviceMeshEnabled` `boolean` Optional Enable service mesh for enhanced inter-service communication with improved performance, reliability, and monitoring. ```yaml theme={null} serviceMeshEnabled: true ``` Useful for workers that need to communicate with other services in your cluster. *** ## `terminationGracePeriodSeconds` `integer` Optional Seconds to wait for graceful shutdown before forcefully terminating the container. ```yaml theme={null} terminationGracePeriodSeconds: 120 ``` Set this to a value higher than your longest expected job. This gives workers time to complete in-progress work before shutdown. *** ## `gpuCoresNvidia` `integer` Optional Allocate NVIDIA GPU cores for ML workloads or GPU-accelerated processing. ```yaml theme={null} gpuCoresNvidia: 1 nodeGroup: gpu-node-group-uuid ``` Requires a node group with GPU-enabled instances. *** ## Complete Example ```yaml theme={null} services: - name: queue-processor type: worker run: npm run worker cpuCores: 1 ramMegabytes: 2048 # Autoscaling autoscaling: enabled: true minInstances: 2 maxInstances: 20 cpuThresholdPercent: 70 memoryThresholdPercent: 80 # Health checks livenessCheck: enabled: true command: ./healthcheck.sh timeoutSeconds: 5 readinessCheck: enabled: true command: ./ready.sh timeoutSeconds: 3 # Cloud connections connections: - type: awsRole role: worker-sqs-access # Service mesh serviceMeshEnabled: true # Graceful shutdown (allow 2 minutes for jobs to complete) terminationGracePeriodSeconds: 120 ``` *** ## Common Use Cases ### Queue Consumer ```yaml theme={null} services: - name: sqs-consumer type: worker run: node src/workers/sqs-consumer.js cpuCores: 0.5 ramMegabytes: 512 instances: 5 terminationGracePeriodSeconds: 60 connections: - type: awsRole role: sqs-consumer-role ``` ### Background Job Processor ```yaml theme={null} services: - name: job-processor type: worker run: bundle exec sidekiq cpuCores: 1 ramMegabytes: 1024 autoscaling: enabled: true minInstances: 2 maxInstances: 10 cpuThresholdPercent: 70 terminationGracePeriodSeconds: 300 ``` ### ML Inference Worker ```yaml theme={null} services: - name: ml-worker type: worker run: python worker.py cpuCores: 4 ramMegabytes: 8192 gpuCoresNvidia: 1 nodeGroup: gpu-node-group-uuid instances: 2 ``` # Advanced networking Source: https://docs.porter.run/applications/configure/advanced-networking Customize NGINX ingress annotations for your Porter web services including read/write timeouts, request body size limits, and WebSocket support ## Customizing Network Settings for an Application[](#customizing-network-settings-for-an-application "Direct link to heading") On Porter, you have the flexibility to customize the NGINX configuration for each of your Web service by adding an "Ingress Annotation" when deploying a web service. This can be found in the "Networking" tab of the web service. Ingress Annotations To add an annotation, add the key-value pairs for an annotation you want to add. For a full list of NGINX annotations you can add to the service, please consult [this documentation](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/). Below are the most common custom networking options we see among our users. ### Setting Custom Read/Write Timeouts[](#setting-custom-readwrite-timeouts "Direct link to heading") Read/write timeouts are very application-specific, and are subject to change on the Porter templates. For example, if you have a websocket application that does not handle dropped connections gracefully, you may be inclined to set your read/write timeout to be much higher than what is standard (\~30 seconds). For those coming from Heroku, the Heroku router enforces a write timeout of 30 seconds, and will keep a connection alive if a single byte is sent within a 55 second window. On Porter, you can configure your own read/write timeouts by adding annotations: ``` nginx.ingress.kubernetes.io/proxy-connect-timeout: "60" nginx.ingress.kubernetes.io/proxy-read-timeout: "60" nginx.ingress.kubernetes.io/proxy-send-timeout: "60" ``` Note: All timeout values are unitless and in seconds - for instance, `nginx.ingress.kubernetes.io/proxy-read-timeout: "120"` sets a valid 120 seconds proxy read timeout. For an explanation of these values: | Annotation | Description | | ------------------------------------------------- | ---------------------------------------------------------------------- | | nginx.ingress.kubernetes.io/proxy-connect-timeout | The timeout for NGINX to establish a connection with your application. | | nginx.ingress.kubernetes.io/proxy-send-timeout | The timeout for NGINX to transmit a request to your application. | | nginx.ingress.kubernetes.io/proxy-read-timeout | The timeout for NGINX to read a response from your application. | It is thus recommended to set these values with the ordering `proxy-connect-timeout <= proxy-send-timeout <= proxy-read-timeout`. ### Client Max Body Size[](#client-max-body-size "Direct link to heading") If you are getting undesired `413 Request Entity Too Large` errors, you can increase the maximum size of the client request by setting the field [client\_max\_body\_size](http://nginx.org/en/docs/http/ngx%5Fhttp%5Fcore%5Fmodule.html#client%5Fmax%5Fbody%5Fsize). You can do this by adding the following annotation: ``` nginx.ingress.kubernetes.io/proxy-body-size: 8m ``` This will set the maximum client request body size to 8 megabytes. To learn more about NGINX units, see [this document](http://nginx.org/en/docs/syntax.html). ### Header Size[](#header-size "Direct link to heading") If you are occasionally getting 500-level errors on certain endpoints, your application may be sending response headers which are too large for NGINX to process. You can try increasing the maximum value of the response header size by setting something like the following (default is `1k`): ``` nginx.ingress.kubernetes.io/proxy-buffer-size: 10k ``` # Autoscaling Source: https://docs.porter.run/applications/configure/autoscaling Scale your web and worker services automatically using CPU and memory thresholds, custom Prometheus metrics, or Temporal task queue depth Porter supports several autoscaling strategies for your web and worker services. This guide covers the available options and when to use each. For configuration-as-code (porter.yaml) autoscaling settings, see [Autoscaling Configuration](/applications/configuration-as-code/services/autoscaling). ## Autoscaling Options | Method | Best For | Trigger | | ------------------ | ----------------------------------- | ------------------------------- | | **CPU/Memory** | General workloads | Resource utilization thresholds | | **Custom Metrics** | Queue processors, API rate limiting | Prometheus metrics | | **Temporal** | Temporal workflow workers | Task queue depth | ## Standard Autoscaling (CPU/Memory) The default autoscaling method scales your service based on CPU and memory utilization. ### Configuration 1. Navigate to your application dashboard 2. Select your service 3. Go to the **Resources** tab 4. Enable **Autoscaling** 5. Configure the settings: * **Min instances**: Minimum number of replicas (e.g., 1) * **Max instances**: Maximum number of replicas (e.g., 10) * **CPU threshold**: Target CPU utilization percentage (e.g., 70%) * **Memory threshold**: Target memory utilization percentage (e.g., 70%) Autoscaling Configuration ### How It Works When either CPU or memory usage exceeds your configured threshold, Porter automatically adds replicas. When usage drops, replicas are removed (down to your minimum). For example, with a 70% CPU threshold: * If average CPU across pods exceeds 70%, new replicas are added * If average CPU drops below 70%, excess replicas are removed * The system maintains \~30% headroom for traffic spikes ## Advanced Autoscaling For workloads that need to scale based on external signals rather than resource usage, Porter offers advanced autoscaling options: ### Custom Metrics (Prometheus) Scale based on application-specific metrics like queue length, request latency, or custom business metrics. **Use cases:** * Message queue consumers (RabbitMQ, Redis, SQS) * Rate-limited API services * Batch processing workers [Set up Custom Metrics Autoscaling →](/applications/observability/custom-metrics-and-autoscaling) ### Temporal Autoscaling Scale Temporal workflow workers based on task queue depth. Porter monitors your Temporal task queues and automatically adjusts worker count. **Use cases:** * Temporal workflow workers * Activity workers with variable load * Event-driven processing pipelines [Set up Temporal Autoscaling →](/applications/configure/temporal-autoscaling) # Basic configuration Source: https://docs.porter.run/applications/configure/basic-configuration Set start commands, allocate CPU and RAM resources, and enable sleep mode for web, worker, and job services from the Porter dashboard These are basic configuration options that are available on all three services - **Web**, **Worker**, and **Jobs**. ## Setting the Start Command You can configure your application service to run any start command by simply editing it from the Porter Dashboard. If your application was built using a `Dockerfile`, the start command you put in here will override the `CMD` directive that is specified in your `Dockerfile`. ## Assigning resources From the **General** tab, you can assign the amount of vCPU and RAM for each of your applications. The maximum amount of resources you can assign to a single application is capped by the size of the virtual machines you are using for the underlying cluster. CPU and RAM Configuration ## Sleep Mode "Sleeping" a service can be enabled from the **Advanced** tab. When a service is put to sleep, it will be stopped and no instances will run. This is useful if you want to keep a service around but don't want to pay for the resources when it's not being used. Note that Sleep Mode is enabled at the service level, rather than at the app level. # HTTPS certificates and custom domains Source: https://docs.porter.run/applications/configure/custom-domains Configure custom domains with automatic Let's Encrypt SSL certificate issuance and renewal for Porter web services on AWS, GCP, and Azure Porter secures all Web services with SSL certificates issued by [Let's Encrypt](https://letsencrypt.org/). Porter will automatically handle the issuance and renewal of your certificates. ## Porter Domains Porter generates placeholder domains for all Web services. These placeholder domains follow the format of `*.onporter.run`. While these domains can be used in production, we highly recommend attaching a custom domain to your production web services. ## Custom Domains Setting up a custom domain involves two steps: setting up a DNS record to point to that domain, and then configuring your Web service to listen on that custom domain: * [DNS Setup](#dns-setup) * [Deploy your Application](#deploying-on-the-custom-domain) ## DNS Setup[](#dns-setup "Direct link to heading") You must first find the DNS name assigned to the load balancer of your cluster. This can be found under the **Networking** tab of your Web Service: Ingress IP Address The DNS records that need to be created for your Web service vary slightly depending on the cloud provider and the DNS provider you are using. Copy this address, as you will need it to create the DNS record. ### Google Cloud and Azure On Google Cloud and Azure, the load balancer that sits in front of your infrastructure has a static IP address. Therefore, you have to create an `A` record in your DNS provider that points to that static IP address. The name of the record should be the subdomain you want to use (e.g. `app.mydomain.com`), and the value should be the IP address of the load balancer that you copied above. ### Amazon Web Services[](#amazon-web-services "Direct link to heading") For clusters deployed on AWS, you need to create a `CNAME` record that points at the DNS name for your load balancer. Please follow instructions below, based on the type of domain you are creating: * Subdomain -> domains of the format `*.porter.run` such as `cloud.porter.run`, `myapp.porter.run` * Apex domain -> top level domains, of the format `porter.run` Create a `CNAME` record on your DNS provider for your desired subdomain, which points to the load balancer URL you have copied above. Make sure you exclude the protocol `http://` and any trailing `/` from the URL string. For example, on Route 53, this looks like the following: ![CNAME record](https://imagedelivery.net/l4LYM_vOYKe7O1NCT_Nc_g/be14bc42-4f63-4d3b-d0eb-5d4d71438700/large "CNAME record") Because AWS creates a load balancer that is assigned a domain name, rather than an IP address, you must use a DNS provider which allows for `ALIAS` records, since `CNAME` records are not supported for apex domains. Create an `ALIAS` record for your root domain which points to the load balancer address that you copied above. If you've purchased your domain through a service like GoDaddy that does not support `ALIAS` records, we recommend that you switch your service to Route 53. Please follow [this guide](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/MigratingDNS.html) to manage your existing domains with Route 53, and then see the instructions below for Route 53 setup. #### Route 53 Instructions[](#route-53-instructions "Direct link to heading") When creating a new record, leave the Record name empty and select the **Alias to Network Load Balancer** option. After you choose the region your EKS cluster is provisioned in, you will be able to select the DNS name of the load balancer that was displayed from the Porter dashboard. Set Record type as **A record** and create the record. ![A Record](https://imagedelivery.net/l4LYM_vOYKe7O1NCT_Nc_g/90f234e2-30f0-463a-8334-fb68cce1b900/large "Screen Shot 2021-01-18 at 6.56.04 PM.png") Once DNS has propagated, you can now [deploy your application using the custom domain](#deploying-on-the-custom-domain)! After you complete the previous steps, it might take up to 30 minutes for DNS records to fully propagate. Please wait before deploying your applications until the DNS propagation is complete. You can check this using tools like [dnschecker.org](https://dnschecker.org) or running `nslookup `. ## Deploying on the Custom Domain[](#deploying-on-the-custom-domain "Direct link to heading") Once the DNS record changes have been propagated, you will be able to attach the custom domain to your application. Click on **Add Custom Domain**, input the custom domain you have just pointed to the load balancer, then hit deploy. In a few minutes, you will be able to view the application on the custom domain, secured with an SSL certificate. # Environment groups Source: https://docs.porter.run/applications/configure/environment-groups Share environment variables and secrets across multiple applications using project-wide groups synced to AWS, GCP, or Azure secret managers An environment group is a set of environment variables and secrets that can be shared across multiple applications. Environment groups are **project-wide** — they can be used across all clusters and cloud accounts within your project. For example, if all of your web services need a shared set of API keys and database credentials, you can create an environment group containing those values and sync it to each service. Environment variables configured directly on an application always take precedence over values from an environment group. This override applies on a per-variable basis — if an app sets `API_KEY=xyz` and a synced environment group has `API_KEY=abc`, the app-level value (`xyz`) is used. ## How Secrets Are Stored Environment group secrets are automatically synced to the secret manager of every cloud account linked to your project that has a running cluster: * **AWS** — AWS Secrets Manager * **GCP** — GCP Secret Manager * **Azure** — Azure Key Vault No secret data is stored on Porter's infrastructure. Secrets only exist in memory on Porter's servers momentarily during creation and updates. If you already manage your secrets in a third-party secret manager, you can sync them into Porter as a read-only environment group instead. See the [Doppler](/integrations/doppler) and [Infisical](/integrations/infisical) integrations. ## Creating an Environment Group You can create a new environment group from the **Env Groups** tab on the Porter dashboard. Click **New Env Group**, enter a name, and add your variables and secrets. Environment group names must be up to 63 characters and may only contain lowercase letters, numbers, and hyphens (`-`). You can also create environment groups from the CLI: ```bash Interactive theme={null} porter env create ``` ```bash Non-Interactive theme={null} porter env create --name production-secrets ``` ```bash With Variables and Secrets theme={null} porter env create --name production-secrets \ -v NODE_ENV=production -v LOG_LEVEL=info \ -s DB_PASSWORD=secret -s API_KEY=sk-123 ``` ## Variables and Secrets Environment groups support two types of values: | Type | Description | Visibility | | ------------- | ------------------------------------------------------------------------------ | --------------------------------------------------------------------- | | **Variables** | Non-sensitive configuration values (max 25 KB per value) | Visible in the dashboard and CLI after creation | | **Secrets** | Sensitive values such as API keys, passwords, and tokens (max 25 KB per value) | Hidden after creation; stored in your cloud provider's secret manager | When creating or updating an environment group, use the lock icon in the dashboard (or the `-s` flag in the CLI) to mark a value as a secret. ## Files Environment groups can also contain files for sensitive data such as certificates or configuration files. Files are managed through the Porter dashboard and are injected into your application's container at the path: ``` /etc/secrets/ ``` ## Syncing Environment Groups to Applications Environment groups can be synced to applications so that when the group is updated, all synced applications are automatically redeployed with the new values. ### From the Dashboard You can sync an environment group to an application during app creation or by navigating to the application's **Env Groups** tab and adding the group. Click **Update app** to apply. ### From porter.yaml Add the `envGroups` field to your `porter.yaml`: ```yaml theme={null} version: v2 name: my-app envGroups: - production-secrets - shared-config services: - name: web type: web run: npm start port: 3000 cpuCores: 0.5 ramMegabytes: 512 ``` Environment groups listed in `envGroups` must already exist in the project before deploying. ## Updating an Environment Group When you update an environment group, all applications synced to it are automatically redeployed with the new values. ### From the Dashboard Navigate to the **Env Groups** tab, click the environment group you want to update, make your changes, and click **Update**. ### From the CLI Use `porter env set` to add or update variables, and `porter env unset` to remove them: ```bash Set Variables theme={null} porter env set --group production-secrets -v LOG_LEVEL=debug -v FEATURE_FLAG=true ``` ```bash Set Secrets theme={null} porter env set --group production-secrets -s DB_PASSWORD=new-password ``` ```bash Remove Variables theme={null} porter env unset --group production-secrets -v OLD_VAR -v UNUSED_VAR ``` ```bash Remove Secrets theme={null} porter env unset --group production-secrets -s ROTATED_KEY ``` ## Version History Every update to an environment group creates a new version. You can view the full history of an environment group — including who made each change and when — from the **Versions** tab on the environment group's page. ### Reverting to a Previous Version If a change introduces a bad value or you need to roll back to a known-good configuration, you can revert an environment group to any previous version from the **Versions** tab. 1. Open the environment group from the **Env Groups** tab. 2. Switch to the **Versions** tab. 3. Find the version you want to restore and click **Revert to v\**. 4. Review the diff between the current version and the target version, then confirm. Reverting does not rewrite history — it creates a **new version** containing the values from the version you selected. The previous versions remain in the history, so you can roll forward again if needed. All applications synced to the environment group are automatically redeployed with the reverted values, just like a normal update. ## Pulling Environment Variables Locally You can pull the contents of an environment group to your local machine for development: ```bash Print to stdout theme={null} porter env pull --group production-secrets ``` ```bash Write to File theme={null} porter env pull --group production-secrets --file .env.local ``` ```bash Variables Only theme={null} porter env pull --group production-secrets -v ``` ```bash Secrets Only theme={null} porter env pull --group production-secrets -s ``` The `--variables` (`-v`) and `--secrets` (`-s`) flags are mutually exclusive. If neither is specified, both variables and secrets are included in the output. ## Listing Environment Groups To see all environment groups in your project: ```bash theme={null} porter env list ``` This displays a table with each group's name, current version, and last updated time. ## Version History Every update to an environment group creates a new version. Porter retains the **10 most recent versions** of each environment group; older versions are pruned automatically along with their underlying secret data in your cloud provider's secret manager. Because applications resolve their environment values against a pinned version of the group, an app that has not redeployed in a long time can fall behind. If a synced application is still pinned to a version older than the 10 most recent, redeploying it will pull values from the current version instead. ## Reverting to a previous version Every update to an environment group creates a new version. If a recent change causes problems — for example, a bad value rolled out to every synced application — you can revert the group to any prior version. Reverting re-applies the target version's variables, secrets, and files as a **new** version on top of the current one. The historical version itself is preserved, so the revert is fully auditable and can itself be reverted. Reverting an environment group automatically redeploys every application synced to it, just like a normal update. Apps pick up the restored values on their next rollout. Reverts are performed through the Porter dashboard from the environment group's version history. The action is admin-scoped — only project admins can revert a group to a previous version. ## Deleting an Environment Group Environment groups can be deleted from the **Settings** tab on the environment group's page in the dashboard. You cannot delete an environment group that is synced to an application. Unsync the environment group from all applications before deleting it. ## CLI Reference For the full list of flags and options, see the [porter env](/standard/cli/command-reference/porter-env) CLI reference. # Secure cloud access Source: https://docs.porter.run/applications/configure/secure-cloud-access Grant Porter applications secure access to AWS, GCP, and Azure resources using workload identity, IAM roles, and IRSA—no static credentials required. Porter services can authenticate to your cloud accounts without you having to embed static access keys or service-principal secrets in environment variables. Instead, each pod assumes a cloud identity at runtime via your cloud provider's native workload-identity mechanism. This page covers how to configure these identities for each supported cloud provider, and how to scope their permissions to follow the principle of least privilege. *** ## AWS Porter uses [EKS Pod Identity](https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html) to associate an IAM role with a service's pod. Your application code can then use the default AWS SDK credential chain (e.g. `DefaultAWSCredentialsProviderChain` in Java, `aws.config.LoadDefaultConfig` in Go, `boto3.Session()` in Python) and credentials will be resolved automatically — no access keys, no `sts:AssumeRole` calls in application code. ### How it works When you attach an IAM role to a service, Porter creates an [EKS Pod Identity Association](https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html) on your cluster that maps the service's Kubernetes service account to the IAM role you specify. At runtime, the EKS Pod Identity agent injects short-lived credentials for that role into the pod. You are responsible for creating the IAM role and granting it the permissions it needs. Porter does not create the role or modify its permissions. ### Step 1 — Create the IAM role In your AWS account, create a new IAM role with two things: 1. A **trust policy** that allows EKS Pod Identity to assume the role: ```json theme={null} { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "pods.eks.amazonaws.com" }, "Action": ["sts:AssumeRole", "sts:TagSession"] } ] } ``` 2. A **permissions policy** scoped to only the AWS APIs and resources your service needs. We strongly recommend following the principle of least privilege: avoid wildcard actions and `Resource: "*"`, and create a dedicated role per service rather than sharing roles across workloads. ### Step 2 — Attach the role to your service In the Porter dashboard, open the service you want to grant access to and scroll to the **IAM Role Connection** section. Toggle the connection on and enter the IAM role name or full ARN: IAM Role Connection UI On the next deploy, your pods can use the role immediately — no application restart logic required. If you manage your app config as code, you can declare the same connection in [`porter.yaml`](/applications/configuration-as-code/overview): ```yaml theme={null} services: - name: api # ... connections: - type: awsRole role: my-app-api-role ``` See [Connections in porter.yaml](/applications/configuration-as-code/services/connections#aws-role-connection) for the full schema. *** ## Azure Porter uses [Azure Workload Identity](https://learn.microsoft.com/en-us/azure/aks/workload-identity-overview) to federate a service's Kubernetes service account with a User Assigned Managed Identity (UAMI) in your Azure subscription. Your application code can then use `DefaultAzureCredential` (or any credential type that supports workload identity) and Azure will issue tokens for the UAMI on the pod's behalf. ### How it works When you attach a managed identity to a service, Porter creates a [federated identity credential](https://learn.microsoft.com/en-us/azure/active-directory/develop/workload-identity-federation) on the UAMI you specify. The credential trusts the AKS cluster's OIDC issuer to vouch for a specific Kubernetes service account. At runtime, the pod presents a projected OIDC token and Azure exchanges it for an access token scoped to the UAMI. You are responsible for creating the UAMI and granting it role assignments. Porter does not create the UAMI or modify its permissions. ### Step 1 — Create a User Assigned Managed Identity In the same Azure subscription as your AKS cluster, create a User Assigned Managed Identity and grant it the Azure RBAC role assignments needed to access the specific resources your service will call. We strongly recommend following the principle of least privilege: scope role assignments to individual resources rather than resource groups or subscriptions, prefer narrow data-plane roles (e.g. `Storage Blob Data Reader`) over broad roles like `Contributor`, and create a dedicated UAMI per service. You don't need to add any federated credentials yourself — Porter creates the one mapping your service's Kubernetes service account to this identity on the next deploy. ### Step 2 — Attach the identity to your service In the Porter dashboard, open the service you want to grant access to and scroll to the **Azure Managed Identity Connection** section. Toggle the connection on and enter the managed identity name and resource group: Azure Managed Identity Connection UI On the next deploy, Porter creates the federated identity credential on the UAMI. From your application code, use `DefaultAzureCredential` from the Azure SDK and Azure will resolve credentials automatically — no client secrets, no certificates. If you manage your app config as code, you can declare the same connection in [`porter.yaml`](/applications/configuration-as-code/overview): ```yaml theme={null} services: - name: api # ... connections: - type: azureManagedIdentity identityName: my-app-api-identity resourceGroup: my-resource-group ``` See [Connections in porter.yaml](/applications/configuration-as-code/services/connections#azure-managed-identity-connection) for the full schema. *** ## GCP Porter can bind a service to a GCP IAM service account in your project. Your application code can then use [Application Default Credentials](https://cloud.google.com/docs/authentication/application-default-credentials) (e.g. `google.auth.default()` in Python, `google.NewClient` in Go, `GoogleCredentials.getApplicationDefault()` in Java), and GCP will issue tokens for the bound IAM service account. ### How it works When you attach a GCP IAM service account to a service, Porter configures the required IAM binding so the service can receive short-lived credentials scoped to the IAM service account. You are responsible for creating the IAM service account and granting it the IAM roles it needs. Porter does not create the service account or modify its project-level permissions. ### Step 1 — Create a GCP IAM service account In the same GCP project as your Porter infrastructure, create an IAM service account and grant it only the IAM roles needed to access the specific resources your service will call. We strongly recommend following the principle of least privilege: prefer narrow predefined roles (e.g. `roles/storage.objectViewer` on a single bucket) over broad roles like `roles/editor`, scope role bindings to individual resources rather than the project, and create a dedicated service account per Porter service. You don't need to add the service identity binding yourself — Porter adds it on the next deploy. ### Step 2 — Attach the service account to your service In the Porter dashboard, open the service you want to grant access to and scroll to the **GCP Service Account Connection** section. Toggle the connection on and enter the fully-qualified service account email: GCP Service Account Connection UI ``` my-app@my-project.iam.gserviceaccount.com ``` On the next deploy, Porter adds the service identity binding on the IAM service account. From your application code, use the standard Google Cloud client libraries and credentials will be resolved automatically — no service account key files, no `GOOGLE_APPLICATION_CREDENTIALS` env var. If you manage your app config as code, you can declare the same connection in [`porter.yaml`](/applications/configuration-as-code/overview): ```yaml theme={null} services: - name: api # ... connections: - type: gcpServiceAccount serviceAccountEmail: my-app@my-project.iam.gserviceaccount.com ``` See [Connections in porter.yaml](/applications/configuration-as-code/services/connections#gcp-service-account-connection) for the full schema. ### Enabling Workload Identity on a node group When you turn on Workload Identity for a node group, GKE deploys a metadata server on those nodes that intercepts calls to the GCE metadata endpoint. Once enabled, pods can no longer reach the node's default Compute Engine service account through that endpoint — they obtain Google credentials through a Workload Identity–bound Kubernetes service account instead. Enabling Workload Identity can interrupt workloads that rely on the node's default service account. If any pod on the node group authenticates to Google APIs using the default node service account (for example, through the GCE metadata endpoint or the default credential chain), it will lose access until it's reconfigured to use a Workload Identity–bound Kubernetes service account. Before enabling it on a node group that already runs production traffic, confirm that none of its workloads depend on the default node service account. If you're not sure, create a new node group with Workload Identity enabled and [schedule your workloads](/cloud-accounts/node-groups#assigning-workloads-to-node-groups) on it to test before changing an existing node group. *** ## Related documentation * [Configuration as code](/applications/configuration-as-code/overview) — Overview of managing Porter apps via `porter.yaml` * [Connections in porter.yaml](/applications/configuration-as-code/services/connections) — Reference for all connection types, including the YAML schema * [AWS EKS Pod Identity](https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html) — AWS documentation * [Azure Workload Identity](https://learn.microsoft.com/en-us/azure/aks/workload-identity-overview) — Azure documentation * [Application Default Credentials](https://cloud.google.com/docs/authentication/application-default-credentials) — GCP documentation # Temporal autoscaling Source: https://docs.porter.run/applications/configure/temporal-autoscaling Automatically scale Temporal worker services based on task queue backlog depth using a Temporal Cloud API key and Porter integration # Temporal Autoscaling If you're using [Temporal](https://temporal.io) for workflow orchestration, Porter can automatically scale your worker services based on task queue depth. This ensures your workers scale up when there's a backlog of tasks and scale down when queues are empty. ## Prerequisites Before configuring Temporal autoscaling, you'll need: * A Temporal Cloud account * The task queue name your workers poll from ## Step 1: Create a Service Account and API Key Porter needs an API key to monitor your task queue depth. We recommend creating a dedicated service account with minimal permissions rather than using a personal API key. ### Required Permission Porter only needs **Read** permission on your namespace. This allows Porter to call `DescribeTaskQueue` to retrieve the queue backlog count for scaling decisions. Read permission does not allow starting, terminating, or modifying workflows. ### Create a Service Account 1. Log in to [Temporal Cloud](https://cloud.temporal.io) 2. Navigate to **Settings** → **Identities** 3. Click **Create Service Account** 4. Configure the service account: * **Name**: A descriptive name (e.g., `porter-autoscaling`) * **Description**: Optional description (e.g., "Used by Porter for autoscaling workers") * **Account Level Role**: Select **Read** (minimum required) * **Namespace Permissions**: Add your namespace with **Read** permission 5. Click **Create Service Account** Temporal Service Account Creation *Creating a service account in Temporal Cloud* ### Create an API Key for the Service Account 1. After creating the service account, navigate to **Settings** → **API Keys** 2. Click **Create API Key** 3. Configure the API key: * **Identity Type**: Select **Service Account** * **Service Account**: Select the service account you created (e.g., `porter-autoscaling`) * **Name**: A name for this key (e.g., `porter-production`) * **Description**: Optional description * **Expiration**: Set an appropriate expiration date 4. Click **Generate API Key** 5. **Copy the API key immediately** — it will only be displayed once Temporal API Key Creation *Creating an API key for the service account* **Important:** Store your API key securely. Porter encrypts the key before storing it. For more details, see the [Temporal Cloud Service Accounts](https://docs.temporal.io/cloud/service-accounts) and [API Keys](https://docs.temporal.io/cloud/api-keys) documentation. **Note:** Porter requires API key authentication and does not support certificate-only (mTLS) authentication. If your Temporal namespace is configured to only allow certificate authentication, contact [Temporal Support](https://support.temporal.io) to enable API keys on your namespace. ## Step 2: Create a Temporal Integration in Porter Before you can use Temporal autoscaling, you need to register your Temporal cluster as an integration in Porter. 1. Navigate to **Integrations** in the Porter dashboard 2. Select the **Temporal** tab 3. Click **Add Integration** 4. Fill in the integration details: * **Name**: A friendly name for this integration (e.g., `production-temporal`) * **Endpoint**: Your Temporal Cloud endpoint (e.g., `my-namespace.a1b2c.tmprl.cloud:7233`) * **Namespace**: Your Temporal namespace (e.g., `my-namespace.a1b2c`) * **API Key**: The API key you created in Step 1 5. Click **Add integration** Temporal Integration Modal *Adding a Temporal integration in Porter* Once created, your Temporal integration will appear in the integrations list and can be used for autoscaling any worker service. Temporal Integration List *Temporal integrations in the Integrations page* ## Step 3: Configure Temporal Autoscaling for Your Service With your Temporal integration set up, you can now configure autoscaling for your worker services. 1. Navigate to your application dashboard 2. Select your worker service 3. Go to the **Resources** tab 4. Enable **Autoscaling** and select **Temporal** as the autoscaling type 5. Configure the autoscaling settings: * **Min instances**: Minimum number of worker replicas (e.g., 1) * **Max instances**: Maximum number of worker replicas (e.g., 20) * **Temporal Integration**: Select the integration you created in Step 2 * **Task queue name**: The name of the task queue your workers poll (e.g., `my-workflow-queue`) * **Target queue size**: The target number of tasks per worker instance Temporal Autoscaling Configuration *Configuring Temporal autoscaling for a worker service* ## How Target Queue Size Works The **target queue size** determines how Porter scales your workers. Porter calculates the desired number of replicas as: ``` desired_replicas = ceil(current_queue_depth / target_queue_size) ``` For example: * If your queue has 100 pending tasks and target queue size is 10, Porter scales to 10 workers * If your queue has 5 pending tasks and target queue size is 10, Porter scales to 1 worker * If your queue is empty, Porter scales to your minimum instances **Choosing a target queue size:** * **Lower values** (e.g., 5-10): More aggressive scaling, lower latency for processing tasks * **Higher values** (e.g., 50-100): More conservative scaling, better resource efficiency ## Example: Document Processing Pipeline Consider a document processing system built with Temporal: ### Document Upload API A web service that receives document uploads and starts Temporal workflows for processing. ```python theme={null} from temporalio.client import Client @app.post("/upload") async def upload_document(file: UploadFile): # Start a Temporal workflow for each uploaded document await temporal_client.start_workflow( ProcessDocumentWorkflow.run, args=[file.filename], id=f"doc-{uuid4()}", task_queue="document-processing" ) return {"status": "processing"} ``` ### Document Processing Worker A worker service that runs the document processing workflows. ```python theme={null} from temporalio.worker import Worker async def main(): client = await Client.connect("your-namespace.tmprl.cloud:7233") worker = Worker( client, task_queue="document-processing", workflows=[ProcessDocumentWorkflow], activities=[extract_text, analyze_content, store_results], ) await worker.run() ``` ### Autoscaling Configuration * Min instances: `1` * Max instances: `20` * Task queue: `document-processing` * Target queue size: `10` With this configuration, Porter will: * Keep at least 1 worker running during quiet periods * Scale up to 20 workers during document upload spikes * Scale based on the actual queue depth, ensuring documents are processed promptly # Zero-downtime deployments Source: https://docs.porter.run/applications/configure/zero-downtime-deployments Prevent downtime during redeployments by configuring health checks, readiness probes, and graceful shutdown behavior for your services Every time an application is redeployed on Porter (through a GitHub action, configuration change, etc), a new set of application instances will replace the old ones. While the update process attempts to prevent downtime, there are additional configuration settings to ensure zero downtime during re-deployment: * [Health Checks](#health-checks) - Ensure new instances are ready before receiving traffic * [Graceful Shutdown](#graceful-shutdown) - Allow old instances to finish processing before termination ## Health Checks Health checks indicate whether an application is healthy and ready to receive traffic. When enabled, traffic won't switch from the old application instance until the health check confirms the new instance is ready. Health checks can be configured in the **Advanced** tab of your service settings. Readiness probes should be used in conjunction with graceful shutdown behavior to control exactly when applications stop receiving traffic. See the [graceful shutdown](#graceful-shutdown) section for more information. ### Web Services For Web services, configure HTTP-based health checks where your application returns: * `200` status code when ready to receive traffic * `500`-level error code when not ready Health checks can be configured in the **Advanced** tab: Health Check Configuration For example, if you configure `/healthz` as your health check endpoint, no traffic will be routed to the web service until that endpoint returns a `200` status code. ### Workers For Worker services, you must set up health checks using custom commands or scripts instead of HTTP endpoints. This is useful for monitoring services like Celery workers that don't expose HTTP endpoints. To configure command-based health checks, create a health check script that: 1. Executes the necessary commands to check your worker's health 2. Returns exit code `0` if the worker is healthy 3. Returns a non-zero exit code if the worker is unhealthy Then specify the path to your health check script in the **Advanced** tab of your service settings. Worker services only support command-based health checks. HTTP health checks are only available for Web services. ## Graceful Shutdown When applications are being re-deployed, old instances receive a `SIGTERM` termination signal. They are then given a **Termination Grace Period**—the number of seconds before the application is forcefully killed. During this period, your application should: 1. Stop accepting new work 2. Complete or close existing connections 3. Exit gracefully The termination grace period can be configured in the **Advanced** tab. ### Web Services Web services will continue to receive traffic until they exit, unless you configure the readiness probe to fail. The recommended graceful shutdown sequence is: 1. When `SIGTERM` is received, immediately return a `500`-level response on your health check endpoint to stop receiving new traffic. 2. Close the server to prevent additional connections. 3. Drain all existing connections before the grace period ends. 4. Exit gracefully after connections are drained. ### Workers For Worker services, implement graceful shutdown using signal handling and file-based coordination: 1. **Trap the SIGTERM signal** in your worker process or an init script for the service. 2. **Write a shutdown marker** to a file when the signal is received, indicating the worker should stop. 3. **Adapt your health check script** to check for this shutdown marker: * If the marker indicates shutdown is in progress, the script should return a non-zero exit code. * This signals to Porter that the worker is no longer healthy and should not receive new work. ## High Availability To ensure your applications are fault tolerant and resilient against failures, configure at least **3 instances** for production workloads. This can be set in the **Resources** tab. If you are using autoscaling, set the minimum replicas to at least 3. # Common errors Source: https://docs.porter.run/applications/debug/common-errors Troubleshoot application restarts caused by memory limits, failing health probes, incorrect start commands, and build failures on Porter ## Application Restarts[](#application-restarts "Direct link to heading") ### Memory Usage[](#memory-usage "Direct link to heading") One of the most common issues for application restarts is that your applications continuously runs out of memory. You can try to allocate more memory to your application, and check the **Metrics** tab to view the memory consumption. If the memory usage continues to hit the memory limit (which is set in the **Resources** tab), increase the memory limit. ### Failing Liveness or Startup Probes[](#failing-liveness-or-startup-probes "Direct link to heading") As documented in the [zero-downtime deployments doc](/applications/configure/zero-downtime-deployments), enabling health checks via liveness probes are a good way to ensure that traffic only reaches healthy application instances. When the liveness probe or startup probes fail, the application will be restarted. There are a few common reasons why the application may fail its health check: * The liveness probe or startup probe are misconfigured. * Your application is experiencing resource pressure, and does not respond to an HTTP probe within 1 second. This is common for runtimes which share a single thread, like Rails or Node. In high-traffic scenarios, the latency of the health check can exceed 1 second, and thus the application will be restarted. * You have not give your application enough time to start up, and thus the application fails its startup probe. * The health check depends on an external service, like a database connection, which is currently unavailable. ### Start Command[](#start-command "Direct link to heading") When the start command isn't set correctly, application logs will never show in the dashboard, and you will see a message in the "System" logs stating that the OCI container runtime is unable to start the process. Make sure that you've set the start command correctly. You can read more about the start command for web applications in the [basic configuration](/applications/configure/basic-configuration#setting-the-start-command). One method to check which commands are set in the `$PATH` of your container is to set the start command to `sleep infinity`, and then use the porter run command to get shell access. From this shell, you could for example run: ``` $ which $ echo $PATH ``` ### Application Issues and Non-Zero Exit Codes[](#application-issues-and-non-zero-exit-codes "Direct link to heading") Your application may be restarting due to an application-level error which is causing the process to exit. To investigate if this may be the cause of application restarts, you can view the logs for failing applications by navigating to the **Events** tab. If your application is killed due to a non-zero exit code, it typically indicates that your application restarted due to an application error, or was killed by an external signal. For an overview on exit codes: * A valid exit code is between 0 and 255, 0 means that the container exited normally. * Generally speaking, if the container exited due to an internal signal then the exit code is between 1 and 128 and if it exited due to an external signal, the exit code is between 129 and 255. * The above will not hold true if the application programmer chooses to follow a different convention of using exit codes. #### Typical exit codes[](#typical-exit-codes "Direct link to heading") * `137`: indicates that the process was killed by `SIGKILL`. The most common reason for this is that your application does not handle graceful shutdown when it receives a `SIGTERM` signal. After receiving `SIGTERM`, your application should close existing connections and terminate with exit code `0`. See the [graceful shutdown doc](/applications/configure/zero-downtime-deployments#graceful-shutdown) for more information. * `1`: indicates common issues. Check container logs for further troubleshooting. For example, this could be the result of `exit(1)`. * `255`: this could either be the result of `exit(-1)` (which is translated to `255`) from the application, or could indicate that the application was forcibly killed by the underlying Kubernetes node. This is common if the application moves between nodes during a node scale-down event. While Porter typically kills processes running on the nodes gracefully, there are rare cases where the containers are abruptly stopped. To avoid downtime in these instances, it is recommended that at least 2 replicas are running for each application instance. * `2`: This could happen because of a misuse of a shell builtin when using Bash. * `126`: A command was invoked that could not be executed by the system. * `127`: Command was not found. Please check your `$PATH` or for a possible typo. * `128`: Invalid argument to `exit()`. * `130`: Process terminated with `Ctrl+C`. **Note:** Normally, an exit code of `128+n` denotes the fatal signal `n` from the standard [Linux interruption signals](https://man7.org/linux/man-pages/man7/signal.7.html). ## Image Pull Errors[](#image-pull-errors "Direct link to heading") Under the hood, every application which runs on Porter is running a [Docker image](https://docs.docker.com/get-started/overview/#images). This Docker image is pulled from a registry, which typically requires authentication credentials. If you are facing an image pull error, make sure you've checked the following items: * The image repository exists in the registry, and the image repository contains an image with the image tag set on Porter * You have connected an image registry to Porter. For more information, see the [deploy overview guide](/applications/deploy/overview) or the doc on [deploying from a Docker registry](/applications/deploy/deploy-from-docker-registry) * Your authentication credentials have not been revoked and have not expired. To check this, navigate to the **Integrations** tab on Porter, and select **Docker Registry**. If you are able to view the list of images for your registry, Porter is able to access that image registry. ## Networking Issues[](#networking-issues "Direct link to heading") ### Frequent Connection Resets/Dropped Connections[](#frequent-connection-resetsdropped-connections "Direct link to heading") This can be caused by a number of issues, which can either be at the application level, or at the networking level: * If your requests take longer than 30 seconds to resolve, or you are running a websocket-based application, the default read and write timeouts may not be long enough. See the [networking configuration doc](/applications/configure/advanced-networking#setting-custom-readwrite-timeouts) for how to increase the read/write timeouts. * If your application sends very large headers as part of the response body, consider increasing the response header size, as documented [here](/applications/configure/advanced-networking#header-size). * If you are seeing dropped connections while redeploying, follow the instructions for [zero-downtime deployments](/applications/configure/zero-downtime-deployments). * If you are seeing `recv` errors in your NGINX logs, the application is sending sending a connection reset message before a response is sent. This can usually be resolved by increasing the `keepalive` value in your application code. ### `413 Request Entity Too Large`[](#413-request-entity-too-large "Direct link to heading") This is caused by the NGINX instance rejecting requests that are too large. See the [networking configuration doc](/applications/configure/advanced-networking#client-max-body-size) for how to resolve these errors. ### `502 Bad Gateway`[](#502-bad-gateway "Direct link to heading") You will see `502 Bad Gateway` when your application is not starting correctly. See [application restarts](#application-restarts) to troubleshoot the error. This could also be a port number error -- make sure that you've set the port number correctly in the `Main` application tab. ### `503 Temporarily Unavailable`[](#503-temporarily-unavailable "Direct link to heading") The most common cause of this error is not setting the port number correctly. If not set correctly, your application will often show `503 Temporarily Unavailable` permanently when visiting the public URL. Make sure that you've set the port number correctly in the `Main` application tab. If the port number is set correctly, this may be shown when there is an application restart: [see above](#application-restarts) for more information. # Building your application Source: https://docs.porter.run/applications/deploy/builds How Porter builds applications from GitHub repositories using GitHub Actions, including build settings, Dockerfiles, and buildpacks When an application is deployed from a GitHub repository, Porter will build your application by opening a PR that contains a GitHub Actions file in your repository. For those who are not familiar with GitHub Actions, we recommend getting a basic overview from [their documentation](https://docs.github.com/en/actions). ## Merging in the PR for GitHub Actions When you create an application, you will be asked to merge in the PR that Porter has opened in your repository. Porter will open a PR that contains the GitHub Actions file from a new branch called `porter-stack`. The GitHub actions file included in the PR will, by default, be triggered on any pushes to the specified git branch. Here's an example file that could be written to your repository: ``` "on": push: branches: - master name: Deploy to storm-king jobs: porter-deploy: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v3 - name: Set Github tag id: vars run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT - name: Setup porter uses: porter-dev/setup-porter@v0.2.0 - name: Deploy stack timeout-minutes: 30 run: porter apply -f ./porter.yaml env: PORTER_CLUSTER: "37" PORTER_HOST: https://dashboard.porter.run PORTER_PR_NUMBER: ${{ github.event.number }} PORTER_PROJECT: "18" PORTER_STACK_NAME: testasdfasdfasdf PORTER_TAG: ${{ steps.vars.outputs.sha_short }} PORTER_TOKEN: ${{ secrets.PORTER_STACK_18_37 }} ``` You can customize this GitHub action to be triggered on different actions than `push`. Please see the GitHub Actions [docs](https://docs.github.com/en/actions) for reference. As long as the steps **Setup porter** and **Deploy stack** are present in your github actions file, you can also prepend or append any steps as desired. ## Viewing your Build Logs You can view the build logs of your application from either the Porter dashboard or the GitHub Actions tab. To view your build logs on GitHub, navigate to the **Actions** tab in your repository. Click on the most recent **Deploy to Porter** workflow to view the build logs of your application. ## Retrying a Build on Failure When builds fail, you can troubleshoot by viewing the logs from the Porter Dashboard or the **Actions** tab on GitHub. To retry a build, simply toggle the option to **Re-run build and deploy on save**, then click on **Save Build Settings**. This will re-trigger the github actions file present in your github repository. You can also trigger a rerun of this GitHub Actions directly from GitHub by clicking on **Re-run failed jobs** button after selecting the job run. ## Build-time Environment Variables Many languages and frameworks require certain environment variables to be present during the build process. **Any environment variable you add to an application on Porter will automatically be piped into your build process; there is no need for you to manually add these environment variables to the GitHub Actions file**. Any supplementary environment variables you add to the GitHub Actions file will also be made available to your build process, however, as long as they are prefixed with `PORTER_`. Please note that `Secrets` will not be made available to your build process. Learn more about `Secrets` [here](/applications/configure/environment-groups). ### Using build-time environment variables for `Dockerfile` builds If you are building your application via `Dockerfile` and require environment variables to be used during the build process, you can use the `ARG` keyword to make these variables available to your `Dockerfile` and the `${env-var-name}` syntax to reference them elsewhere in the file. Example snippet from a `Dockerfile`: ``` ... ARG PORTER_ENV_VARIABLE RUN echo "Here is my env variable: ${PORTER_ENV_VARIABLE}" ... ``` Note that if you are building via buildpacks, no additional configuration is required to make your build-time environment variables available to your build process. ## Storing Container Images When your application has built successfully, Porter will automatically push the resulting container images into a container registry in your connected cloud account and update your application with the new build. ## Using Buildkit For certain Dockerfile-based builds, Buildkit may be required to build successfully, such as when using `COPY --chown`. To enable buildkit, set the `DOCKER_BUILDKIT` environment variable to `1`. This can be set at the Github Actions Workflow-level with the following snippet at the top-level of your Github Actions file: ``` # ... other parts of the workflow file go here env: DOCKER_BUILDKIT: "1" ``` # Configuring application services Source: https://docs.porter.run/applications/deploy/configuring-application-services Set up web, worker, and job services with start commands, port mappings, resource allocation, and instance scaling from the Porter dashboard A Porter application consists of one or more **services**. Services are the individual processes that make up your application, and they come in three types: **Web services** handle HTTP traffic. If your application serves a website, API, or any other HTTP endpoint, it runs as a web service. Web services can be exposed to the internet with custom domains, or kept private within your cluster for internal communication. **Worker services** run continuously in the background without accepting HTTP traffic. Use workers for queue processors, background job runners, event consumers, or any long-running process that doesn't need to respond to web requests. **Job services** run on a schedule or on-demand, execute their task, and then stop. Use jobs for database maintenance, report generation, cleanup tasks, or any work that runs periodically rather than continuously. Service type selector You can add additional services by clicking **Add Service** and selecting the appropriate type. Each service within an application shares the same codebase and build, but can have different start commands, resource allocations, and configurations. Service names must be lowercase letters, numbers, and hyphens only. They're used internally for routing and identification. *** ## Start command and port Every service needs to know how to run your application. ### Start command The start command tells Porter what process to run inside your container. For GitHub deployments, Porter often detects this automatically based on your framework. For Docker deployments, leave the start command empty to use your image's default CMD, or specify a command to override it. If your image supports multiple modes, you can run the same image with different commands for each service: * Web service: `npm start` or leave empty for default * Worker service: `npm run worker` * Job service: `npm run cleanup` ### Port Web services require a port number—the port inside the container where your application listens for HTTP traffic. This must match what your application actually binds to, not an external port. Common ports include 3000 (Node.js), 8080 (many frameworks), 80 (nginx), and 5000 (Flask). Check your application's configuration or Dockerfile if you're unsure. *** ## Resource allocation and scaling Every service needs compute resources. Porter lets you configure exactly how much CPU and memory each service receives, and how it scales under load. ### CPU and memory CPU is measured in cores, configurable from 0.1 (one-tenth of a core) up to 8 cores. Memory is measured in megabytes, from 128 MB up to 16 GB. The defaults (0.5 cores and 1 GB of memory) work well for lightweight services. Increase these values for compute-intensive workloads or applications with large memory footprints. Resource allocation sliders for CPU and RAM These values represent guaranteed resources. Your service will always have access to at least this much CPU and memory, regardless of what else is running in the cluster. Note that the memory value is also a hard limit. If your service exceeds it, it will be restarted. ### Node groups If your cluster has more than one node group to pick from, you can select them here. When you choose a node group with GPU support, an additional slider appears for configuring GPU allocation. For most applications, the default node group is appropriate. ### Scaling By default, Porter runs a single instance of each service. For production workloads, you'll typically want multiple instances for redundancy and to handle traffic spikes. See [Autoscaling](/applications/configure/autoscaling) for a complete guide. *** ## Networking and domains Web services can be exposed to the internet or kept private within your cluster. ### Public services By default, web services are public, i.e., accessible from the internet. Porter provisions a URL where your service is reachable immediately after deployment. Public/private toggle ### Private services Toggle a service to **private** when it should only be reachable by other services in your cluster. Private services are useful for internal APIs, admin interfaces, or services that sit behind a public-facing gateway. Private services get internal DNS names that other services in your cluster can use to communicate. ### Custom domains To serve your application on your own domain, add it in the Domains section. You can configure multiple domains for a single service—useful for handling `www` and non-`www` versions, or serving the same application on different domains. After adding a domain, configure DNS by creating a CNAME record (or an A record for apex domains) pointing to your cluster's ingress IP address. Porter displays this address with a copy button. DNS propagation typically takes a few minutes, though it can occasionally take longer. Custom domain configuration with DNS instructions Porter automatically provisions and renews SSL certificates for your custom domains using Let's Encrypt. ### Advanced routing with NGINX annotations For complex routing scenarios, you can add custom NGINX ingress annotations. These key-value pairs are applied directly to the Kubernetes ingress resource, giving you access to NGINX's full feature set. Common uses include custom rewrite rules, rate limiting, authentication requirements, CORS headers, and proxy buffer configuration. The annotation keys follow the `nginx.ingress.kubernetes.io/` prefix convention. Custom NGINX annotations *** ## Environment variables and secrets Most applications need configuration values that vary between environments: database URLs, API keys, feature flags, and other settings. ### Adding variables On the app configuration page, expand the **Environment variables** accordion to define key-value pairs that become environment variables in your running containers. Type the variable name and value, and click the lock icon to mark a value as a secret. Environment variable editor with lock icons The distinction between variables and secrets affects visibility in the Porter dashboard. Secret values cannot be viewed after they're set. You can update them, but not retrieve them. Both are stored securely and injected into your containers at runtime. ### Environment groups If you have variables shared across multiple applications (like a database connection string or third-party API key) you can organize them into environment groups. Select existing groups from the dropdown to sync their variables into your application. Environment groups selector When you update a variable in an environment group, Porter automatically triggers a deployment for all applications using that group. ### Uploading .env files For applications with many environment variables, you can upload an existing `.env` file rather than entering each variable manually. Click **Upload an .env file** and paste in your file contents. Porter parses the `KEY=VALUE` format, skipping comments and empty lines. .env file upload modal ### Copying variables as .env You can export your application's environment variables in `.env` format by clicking **Copy as .env** in the environment variables section. This opens a modal displaying all variables formatted as `KEY=VALUE` pairs, ready to copy to your clipboard. Secret variables with hidden values appear as commented lines at the bottom of the output (e.g., `# SECRET_KEY=`). A banner displays the count of hidden secrets so you know which values are excluded. To include secret values in the export, you must first reveal them in the environment variables editor. This is useful when you need to replicate an application's configuration locally, share it with a teammate, or use it as a starting point for a new application. *** ## Health checks Health checks enable zero-downtime deployments by ensuring new instances are ready before receiving traffic. When enabled, Porter waits for your health endpoint to return a successful response before routing traffic to a new instance, and automatically restarts instances that become unhealthy. Health check configuration Configure the health check with an HTTP path (like `/health` or `/api/health`) that your application exposes. The endpoint should return a 200-level status code when the service is ready to handle requests. For worker services, health checks use a command instead of an HTTP endpoint. Specify a shell command that exits with code 0 when the worker is healthy. The **initial delay** gives your application time to start up before health checks begin. Set this higher if your application has a slow initialization process. The **timeout** determines how long Porter waits for a response before considering the check failed. For best practices on combining health checks with graceful shutdown, see [Zero-Downtime Deployments](/applications/configure/zero-downtime-deployments). ### Metrics scraping If your application exposes Prometheus metrics, Porter can scrape and forward them to your monitoring infrastructure. Enable metrics scraping and specify the port and path where your application serves metrics (commonly `/metrics` on the application port or a dedicated metrics port). See [Custom Metrics Autoscaling](/applications/observability/custom-metrics-and-autoscaling) for details on using these metrics for autoscaling. Metrics scraping configuration ### Sleep mode For non-production environments, sleep mode lets you pause a service to save costs. Sleeping services maintain their configuration but stop running instances. This is useful for staging environments that don't need to run overnight or on weekends. *** ## Pre-deployment jobs Some deployments need setup work before the main application starts, most commonly, database migrations. Pre-deployment jobs (also called migration jobs) run after your new code builds but before traffic routes to new instances. Pre-deployment job configuration Enable the pre-deployment job and configure a start command for your migration script: for example, `npm run migrate`, `python manage.py migrate`, or `bundle exec rails db:migrate`. Pre-deployment jobs have their own resource allocation separate from your application services. Migrations are typically short-lived but may need more memory than your running application, especially for large data transformations. Configure an appropriate timeout based on how long your migrations typically take. The job must complete successfully before deployment continues. If it fails, the deployment halts, and your previous version continues running—your users never see a partially-migrated state. *** ## Scheduled jobs and workers Not every workload serves HTTP traffic. Porter supports background workers and scheduled jobs for processing that happens outside the request-response cycle. ### Workers Workers run continuously, processing tasks from queues, handling events, or performing ongoing background work. Configure a worker with a start command that runs your processing logic: for example, `python worker.py` or `node src/consumer.js`. Worker service configuration Workers support the same resource allocation and autoscaling options as web services. For queue-based workers, custom autoscaling with KEDA lets you scale based on queue depth, adding workers when messages back up and removing them when the queue empties. ### Jobs Jobs run on a schedule and exit when complete. The cron schedule field accepts standard cron syntax with five fields: minute, hour, day of month, month, and day of week. Porter displays a human-readable description of your schedule as you type. Job configuration with cron schedule Some example schedules: * `0 0 * * *` runs daily at midnight * `0 */4 * * *` runs every 4 hours * `0 9 * * 1-5` runs at 9 AM on weekdays * `*/15 * * * *` runs every 15 minutes Configure the timeout to set a maximum execution time. Jobs exceeding this limit are terminated, preventing runaway processes from consuming resources indefinitely. The **concurrent execution** toggle controls whether multiple instances of the same job can run simultaneously. Disable this for jobs that shouldn't overlap. **Suspend cron job** temporarily pauses the schedule without removing the job configuration, useful during maintenance windows. # Connect GitHub Source: https://docs.porter.run/applications/deploy/connect-github Install the Porter GitHub App so Porter can access a repository, detect its applications, and create deployment workflows Before calling `create_app` with `source` and `build`, confirm that the Porter GitHub App is installed on the repository. The MCP server cannot install it. If it is not installed, ask the user to complete the dashboard and GitHub steps on this page. Do not author a `porter.yaml` to create the application; `create_app` handles that, and the CLI and configuration-as-code paths apply only when the user asks for them. Porter needs its GitHub App installed on a repository before it can detect applications, build from source, or create a GitHub Actions deployment workflow. This prerequisite applies whether you create the application with an agent or in the dashboard. In the Porter dashboard, open your project and click **Create Application**, then click **Connect repositories**. GitHub opens the authorization and installation flow. Choose the GitHub account or organization that owns the repository and authorize the Porter GitHub App. Depending on your GitHub organization settings, you may need to request the installation and wait for an organization administrator to approve it. Grant Porter access to all repositories or select the specific repositories you want to deploy. After installation, return to Porter. You can now [deploy from the GitHub repository](/applications/deploy/deploy-from-github-repo) with an agent or the dashboard. # Deploy from a Docker registry Source: https://docs.porter.run/applications/deploy/deploy-from-docker-registry Deploy pre-built container images from Amazon ECR, Google GAR, Azure ACR, Docker Hub, or any OCI registry to Porter without managing builds If you already have a container image built and pushed to a registry, Porter can deploy it directly without managing your build process. This approach works well when you have an existing CI/CD pipeline that produces Docker images, or when you're deploying third-party images that you don't build yourself. Porter supports all major container registries including Amazon Elastic Container Registry (ECR), Google Artifact Registry (GAR), Azure Container Registry (ACR), Docker Hub, and any registry accessible via a standard Docker image URL. This guide covers deploying pre-built container images. If you want Porter to build your application from source code, see [Deploy from a GitHub repository](/applications/deploy/deploy-from-github-repo). Porter runs a hosted MCP server at [https://mcp.porter.run](https://mcp.porter.run). If the user asks for the dashboard, follow the steps on this page. Otherwise, if you are connected to the MCP server, create the application with the `create_app` tool, passing `image` with the repository and tag. Porter deploys the prebuilt image directly — there is no pull request to merge and the Porter GitHub App is not required. `image` is mutually exclusive with `source` and `build`; use `source` and `build` instead when Porter should build the application from a GitHub repository. *** ## Quick deploy Deploying a container image takes just a few steps since you're skipping the build process entirely. ### Specify your image From the Porter dashboard, navigate to your project and click **Create Application**. Porter defaults to the GitHub deployment flow. To deploy from a container registry instead, click **Or, deploy from a container registry** in the top right. GitHub deployment page with container registry link You'll see two input fields: one for the image repository URL and one for the tag. Enter the full path to your image in the repository field. The format depends on your registry: * **Docker Hub**: `docker.io/username/image` or just `username/image` * **Amazon ECR**: `123456789.dkr.ecr.us-east-1.amazonaws.com/my-app` * **Google Artifact Registry**: `us-docker.pkg.dev/project-id/repo/image` * **Azure Container Registry**: `myregistry.azurecr.io/my-app` In the tag field, enter the specific version you want to deploy—this might be `latest`, a semantic version like `1.2.3`, a git commit SHA, or any tag you've pushed to your registry. Container registry image URL and tag inputs ### Review your application Once you've entered both the image URL and tag, Porter creates an application configuration for you. The application name is derived from your image name. Deploying `docker.io/myorg/api-server:v2.1` creates an application called `api-server`. Application card showing derived name and Docker image Porter provides sensible defaults for resources: 0.5 CPU cores, 1 GB of RAM, and a single instance. Since Porter can't inspect your pre-built image the way it can with source code, you'll need to specify the port your container listens on (for web services) and optionally override the start command if your image's default CMD isn't what you want to run. ### Deploy Once the application is created, you can review the app's start command and port, and click the configure button (gear icon) on the card to see other pre-configured values. When ready, deploy using the **Deploy** button. Application card with configure and deploy buttons Porter pulls your image and starts running it. The sections below cover customizing the configuration when you need more control. *** ## Customizing your deployment Since Porter isn't building your image, configuration focuses on how to run your container rather than how to build it. ### Understanding the container registry flow When you deploy from a container registry, Porter: 1. Pulls your specified image from the registry 2. Runs it with your configured services, resources, and environment 3. Manages scaling, health checks, and networking There's no build step, no Dockerfile parsing, and no GitHub Actions workflow. Updates happen when you push a new image tag and tell Porter to deploy it (covered in the [Updating Your Application](#updating-your-application) section). This makes container registry deployments faster to set up, but requires you to manage your own build pipeline. It's ideal when you already have CI/CD infrastructure or when deploying images you don't control. *** ### Configuring services A Porter application consists of one or more **services**—web services for HTTP traffic, workers for background processing, and jobs for scheduled tasks. By default, Porter configures a single web service. For a complete guide to service types, resource allocation, networking, environment variables, health checks, and more, see [Configuring application services](/applications/deploy/configuring-application-services). *** ## Updating your application When you push a new image tag to your registry, Porter doesn't automatically deploy it. You have two options for triggering updates. ### Manual updates From your application's dashboard in Porter, you can change the image tag to deploy a different version. This is useful for one-off deployments or rollbacks. Update tag interface ### CLI updates For automated deployments, use the Porter CLI to update your application's image tag: ```bash theme={null} porter app update-tag APP_NAME --tag TAG_NAME ``` Replace `APP_NAME` with your application name in Porter and `TAG_NAME` with the new image tag you want to deploy. ### CI/CD integration The most common pattern is adding the Porter CLI command to the end of your existing CI/CD pipeline. After your pipeline builds and pushes a new image, it calls Porter to deploy that tag. For example, in a GitHub Actions workflow: ```yaml theme={null} - name: Build and push image run: | docker build -t myregistry.com/myapp:${{ github.sha }} . docker push myregistry.com/myapp:${{ github.sha }} - name: Deploy to Porter run: porter app update-tag myapp --tag ${{ github.sha }} env: PORTER_TOKEN: ${{ secrets.PORTER_TOKEN }} ``` This keeps your existing build process intact while letting Porter handle the deployment. You maintain full control over when and how images are built, and Porter takes over once you're ready to deploy. # Deploy from a GitHub repository Source: https://docs.porter.run/applications/deploy/deploy-from-github-repo Connect a GitHub repository to Porter for automatic builds and deployments with framework detection, branch selection, and CI/CD triggers Porter connects to your GitHub repositories to build and deploy your applications. When you connect a repository, Porter detects your app's framework, identifies services, and configures sensible defaults. This guide covers deploying applications from GitHub. If you're deploying from a container registry instead, see [Deploy from a Docker registry](/applications/deploy/deploy-from-docker-registry). Porter runs a hosted MCP server at [https://mcp.porter.run](https://mcp.porter.run). If its tools are not available in this session, run `curl -fsSL https://agents.porter.run | sh` to install and register them, then restart the client. If the user asks for the dashboard, the CLI, `porter apply`, or configuration-as-code, follow that path on this page. Otherwise, if you are connected to the MCP server, create the application with the `create_app` tool and do not author a `porter.yaml`. Pass `source` and `build` to deploy from a GitHub repository; this creates the app on a placeholder image and opens a GitHub Actions pull request that the user merges to trigger the first real deploy. The Porter GitHub App must already be installed on the repository — the MCP server cannot install it. *** ## Quick deploy Ask an agent connected to the Porter MCP server to create the application for you. It detects your build settings from the codebase and opens a pull request that you merge to deploy. * [Connect your agent to the Porter MCP server](/mcp/overview). `curl -fsSL https://agents.porter.run | sh` does this for you and installs Porter's skills. * [Install the Porter GitHub App](/applications/deploy/connect-github) on the repository you want to deploy. The MCP server cannot install the GitHub App for you. ```text theme={null} Deploy this GitHub repository to Porter. Detect the build method and application settings from the codebase. ``` The agent resolves your Porter project and cluster, then calls `create_app` with the repository source and build configuration. Porter creates the application with a placeholder image and opens a pull request containing a GitHub Actions workflow. Review and merge the pull request that the agent returns. Merging it triggers the workflow that builds your code and runs the first real deployment. For most applications, Porter's automatic detection handles the heavy lifting. Here's how to get your application running: ### Connect your repository From the Porter dashboard, navigate to your project and click Create Application. Porter attempts to deploy from a GitHub repository by default. Source selection screen If this is your first time deploying from GitHub, [install the Porter GitHub App](/applications/deploy/connect-github). You can grant access to all repositories or select specific ones. Porter only needs read access to detect your code, and write access to set up automated deployments. GitHub repository and branch selector Once connected, select the repository containing your application code. Porter auto-selects the repository's default branch (usually `main` or `master`) so you can start deploying immediately. If you need a different target for non‑production, use the Branch selector to switch to `dev`, `staging`, or any other branch. ### Review detected applications After you select a repository and branch, Porter scans your code. It identifies frameworks and languages, locates Dockerfiles, and determines the required services. Detection in progress Within a few seconds, you'll see a list of detected applications. Each card shows the app name (from your repo or directory), the detected framework or build method, and the path in your repository. If the detected build method isn't right for your application, click the configure button (gear icon) on the card to change it. Detected applications list For a simple repository with a single application, you'll typically see one card. For monorepos containing multiple services, Porter detects each application separately. For example, a Node.js API in `/api`, a React frontend in `/web`, and a Python worker in `/jobs` each appear as distinct applications you can configure independently. #### Previously deployed apps in monorepos When you deploy from a monorepo that already has some services running on your cluster, Porter automatically identifies which detected applications are already deployed and hides them from the main list. This keeps the view focused on new applications you haven't deployed yet. A banner at the top summarizes what was detected. For example, if Porter finds 5 applications but 3 are already deployed, the banner indicates that 2 new applications were configured and 3 are already deployed. To view the already-deployed applications, expand the **Show N previously deployed app(s)** section below the application list. Each previously deployed app card shows its name, build path, and the name of the existing deployment it matches. If you want to re-create an already-deployed application (for example, to deploy a second instance with different configuration), click **Add** on its card. A confirmation dialog appears letting you know the build context is already deployed, and you can click **Create anyway** to add it to your new application list. Porter matches detected applications to existing deployments using the build path and build method. If you change the build path of an existing application, Porter may not recognize it as already deployed. If Porter didn't detect an application you expected, or if you want to add another manually, click Add Application. To edit a detected application, click the configure button (gear icon) on the card to make updates. Settings button on application card ### Deploy Once applications are detected, you can review each app's start command and port, and open each app's configuration page to see other pre‑configured values. When ready, deploy using the "Deploy X applications" button. Porter creates a GitHub Actions workflow in your repository that handles building and deploying your application on every push to your selected branch. Your first deployment starts as soon as you merge Porter's GitHub Actions pull request. That's it for a basic deployment. Porter has configured your application with production-ready defaults: appropriate resource allocation, a web service listening on the detected port, and automatic builds on every commit. The sections below cover customizing these defaults when you need more control. Deploy the application from a `porter.yaml` file: ```bash theme={null} porter apply -f porter.yaml ``` Learn how to define and deploy an application in the [configuration-as-code guide](/applications/configuration-as-code/overview). *** ## Customizing your deployment Porter's defaults work well for many applications, but you have full control over every aspect of your deployment. The following sections explain each configuration area in detail. ### Build configuration Porter needs to know how to turn your source code into a runnable container. There are two approaches: Docker (via Dockerfiles) and buildpacks. #### Docker builds If your repository contains a Dockerfile, Porter can use it to build your application. This gives you complete control over the build process and is the right choice when you have custom system dependencies, need a specific base image, or have an application that buildpacks don't support. When you select Docker as your build method, you'll need to specify the path to your Dockerfile relative to the repository root. If your Dockerfile is at the root level, this is simply `Dockerfile`. For monorepos, it might be `./api/Dockerfile` or `./frontend/Dockerfile`. Dockerfile path selector #### Buildpacks Buildpacks automatically detect your application's language and dependencies, then build an optimized container image without requiring you to write a Dockerfile. Porter supports buildpacks for Node.js (including Next.js), Python (Flask, FastAPI, Django), Ruby (Rails), and Go. When Porter detects a supported framework, it selects the appropriate buildpack automatically. You'll see the detected framework displayed on the application card. If the detection isn't quite right, you can override it by selecting a different framework from the dropdown in an application's configuration page. Build method selector Buildpacks work well when your application follows standard conventions for its framework. They handle dependency installation, asset compilation, and runtime configuration automatically. #### Build path The build path determines which directory Porter uses as the root for your build. For most repositories, this is `./` (the repository root). In a monorepo, you'll typically set this to the directory containing the specific application, like `./api` or `./frontend`. The build path affects where Porter looks for your Dockerfile (if using Docker builds) and which files are available during the build process. Build path selector *** ### Configuring services A Porter application consists of one or more **services**—web services for HTTP traffic, workers for background processing, and jobs for scheduled tasks. By default, Porter configures a single web service for detected applications. For a complete guide to service types, resource allocation, networking, environment variables, health checks, and more, see [Configuring application services](/applications/deploy/configuring-application-services). *** ## What happens when you deploy When you click Deploy, Porter initiates several processes: First, Porter creates a GitHub Actions workflow file in your repository at `.github/workflows/porter.yml`. This workflow triggers on pushes to your selected branch, building your application and deploying it to Porter. You'll see a pull request created with this workflow file, and merging this PR enables automated deployments. The initial deployment builds your application image using the configured build method and starts your services with the specified configuration. You can monitor deployment progress from the application dashboard, which shows build logs, deployment status, and any errors that occur. Once deployed, subsequent pushes to your branch trigger automatic rebuilds and deployments. Each deployment creates new container instances, runs health checks (if configured), and shifts traffic only after the new instances are ready. From your application dashboard, you can view logs, monitor resource usage, check deployment history, and make configuration changes. Configuration changes from the dashboard trigger new deployments but do not rebuild your application; rebuilds only happen when triggered from GitHub. # Deploy multiple apps from one build Source: https://docs.porter.run/applications/deploy/multiple-deploys-from-same-build Create a custom GitHub Actions workflow that builds a single container image and deploys it across multiple Porter applications efficiently Sometimes you may want to run multiple applications that are using the same build image. In these cases, it may be more efficient to create a custom GitHub workflow that will build the image once and then deploy the applications from the same image. ``` "on": push: branches: - main env: PORTER_BASE_APP_NAME: base-app PORTER_BASE_IMAGE_REPOSITORY_URI: PORTER_CLUSTER: PORTER_DEPLOYMENT_TARGET_ID: PORTER_HOST: https://dashboard.porter.run PORTER_PROJECT: PORTER_TOKEN: ${{ secrets. }} jobs: build_image: runs-on: ubuntu-latest steps: - name: Checkout Code uses: actions/checkout@v4 - name: Setup Porter uses: porter-dev/setup-porter@v0.2.0 - name: Build Image from Dockerfile run: exec porter app build $PORTER_BASE_APP_NAME --build-method docker --build-context . --dockerfile ./Dockerfile --tag $GITHUB_SHA - name: Push Image to Registry run: exec porter app push $PORTER_BASE_APP_NAME --tag $GITHUB_SHA deploy_apps: runs-on: ubuntu-latest needs: [build_image] strategy: matrix: app: ["base-app", "app-with-same-image-1", "app-with-same-image-2"] steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Porter uses: porter-dev/setup-porter@v0.2.0 - name: Deploy App run: exec porter app update ${{ matrix.app }} --image-repository $PORTER_BASE_IMAGE_REPOSITORY_URI --tag $GITHUB_SHA ``` To set up this workflow, you need to: 1. Choose one of your applications to serve as the base application (`PORTER_BASE_APP_NAME`). The built images will be pushed to the repository for that application. You can choose any application with the shared build image. 2. Get the repository URI for the base application and set `PORTER_BASE_IMAGE_REPOSITORY_URI`. 3. Set the other fields including `PORTER_CLUSTER`, `PORTER_PROJECT`, `PORTER_DEPLOYMENT_TARGET_ID`, and `PORTER_TOKEN`. If you previously created a GitHub workflow for the base application, you should be able to use the same values. 4. Update the `deploy_apps` job to include the names of the applications that will be deployed from the same image (`deploy_apps.strategy.matrix.app`). # Deploying applications Source: https://docs.porter.run/applications/deploy/overview Learn how applications and services work on Porter, then choose how to deploy your application Porter runs a hosted MCP server at [https://mcp.porter.run](https://mcp.porter.run). If the user asks for the dashboard, the CLI, `porter apply`, or configuration-as-code, follow that path. Otherwise, if you are connected to the MCP server, create the application with the `create_app` tool and do not author a `porter.yaml`. Pass `source` and `build` to deploy from a GitHub repository; this creates the app on a placeholder image and opens a GitHub Actions pull request that the user merges to trigger the first real deploy. The Porter GitHub App must already be installed on the repository — the MCP server cannot install it. Once you've provisioned infrastructure in your cloud account, you can deploy your first application. For an end-to-end guide, see the [Quickstart](/getting-started/quickstart#step-4:-create-your-first-application). ## Applications and services Porter deployments have two core concepts: Applications and Services. An **Application** groups one or more **Services**. Every service in an application shares the same container image and environment variables. Porter supports three [types of services](/applications/deploy/types-of-services). Use separate applications for services that need different container images or environment variables. ## Choose a deployment path Install the Porter GitHub App before deploying from a repository. Let an agent or the dashboard detect, build, and deploy your source code. Run a pre-built container image from an OCI registry. Define and deploy an application from a version-controlled `porter.yaml` file. # Pre-deploy jobs Source: https://docs.porter.run/applications/deploy/pre-deploy-jobs Run database migrations or setup tasks after each build but before deployment using pre-deploy jobs in the dashboard or porter.yaml A pre-deploy job is an optional task that runs after each build, but before all your services are deployed. It is commonly used for preliminary tasks which are required for your services to run, such as a database migration or seeding. **Note:** any commands that are used to build your application should *not* be included in a pre-deploy job, as the build will already be completed by the time the pre-deploy job is run. You can specify the pre-deploy job for your application both in the Porter dashboard and in your `porter.yaml` file. ## Configuring pre-deploy in the Porter dashboard You should see the pre-deploy section at the top of your application's **Overview** tab: Add predeploy from UI Alternatively, when creating a new application, you will see the same section in the creation flow. From here, you can specify the start command (e.g., `bash ./migrate-db.sh`) as well as resources allocated for the job. After updating your application, the pre-deploy command will be run after the next build. ## Configuring pre-deploy in the porter.yaml file If you are new to `porter.yaml`, start [here](/applications/configuration-as-code/overview). Otherwise, see [here](/applications/configuration-as-code/services/predeploy) for information about configuring a pre-deploy job in your `porter.yaml` file. ## Tracking pre-deploy progress Your pre-deploy job will run after your build completes successfully. You can track its progress in the **Activity** tab of your application: Predeploy activity feed You can view logs of the pre-deploy job while it runs or after it completes: Predeploy logs # Rollbacks Source: https://docs.porter.run/applications/deploy/rollbacks Revert your Porter application to a previous successful deployment version from the Activity tab when an undesired change has been deployed An application rollback may be necessary if a change was deployed that is not desired. Luckily, it is easy to rollback your application to a previous state. In the **Activity** tab of your application, navigate to the **Deploy** event you wish to rollback to, and click the revert button: Rollback Note that only successful deployments are able to be rolled back to. # Types of services Source: https://docs.porter.run/applications/deploy/types-of-services Understand the three Porter service types: web services for HTTP traffic, workers for background processing, and jobs for scheduled tasks There are three types of services you can deploy on Porter: **web services**, **workers**, and **jobs**. ### Web Service[](#web-service "Direct link to heading") Web services are long-running processes that are exposed to external or internal traffic. This includes any web servers that are serving requests on a domain. Web services are, by default, exposed on a domain automatically generated by Porter, which follows the form of `*.onporter.run`. You can add a custom domain to your web service - Porter will automatically [secure your endpoints with SSL certificates](/applications/configure/custom-domains). Alternatively, you can expose your web service to only internal traffic (i.e. accessible only by other services inside the same cluster). ### Worker[](#worker "Direct link to heading") Worker processes are constantly running processes that are exposed to neither external nor internal traffic. Workers have no URLs or ports - they're best suited for background processes, queuing systems, etc. Most of the configuration options are identical to web applications, excluding the options that manage the endpoint. ### Jobs and Cron Jobs[](#jobs-and-cron-jobs "Direct link to heading") Jobs are processes that run to completion. They're best suited for ephemeral tasks such as database migration or clean up scripts. On Porter, you can run either **one-off jobs** or **cron jobs**. One-off jobs can be triggered manually through the dashboard or using the CLI. Cron jobs run periodically on a schedule specified as a cron expression. # Using other CI tools Source: https://docs.porter.run/applications/deploy/using-other-ci-tools Deploy Porter applications from CircleCI, GitLab, Travis CI, or any CI tool using the Porter CLI Docker image and environment variables You can use other CI tools, such as CircleCI, Travis CI, or Gitlab, to deploy your application. These CI tools support running any Docker image as part of a CI workflow. Porter maintains `ghcr.io/porter-dev/releases/porter-cli:latest`, a public Docker image that contains the Porter CLI. This allows you to run Porter CLI commands easily as part of any workflow. The Porter CLI requires that the following environment variables are set in order to target a specific application: ``` PORTER_TOKEN PORTER_PROJECT PORTER_CLUSTER ``` These environment variables can be set by logging into the Porter CLI and running `porter config`. ## Examples[](#examples "Direct link to heading") ### CircleCI[](#circleci "Direct link to heading") It is easiest to create a [CircleCI Context](https://circleci.com/docs/2.0/contexts/) for each Porter cluster in order to set environment variables for a CircleCI job. In CircleCI, set the following environment variables in a context: ``` PORTER_TOKEN PORTER_PROJECT PORTER_CLUSTER ``` These environment variables can be found after running `porter config` from the Porter CLI. Next, you can create the following CircleCI config file to a desired branch of your repository: ```yaml theme={null} version: 2.1 jobs: porter: docker: - image: ghcr.io/porter-dev/releases/porter-cli:latest steps: - checkout - setup_remote_docker: version: 19.03.13 - run: name: "Update Porter application" command: "porter app update-tag --tag $CIRCLE_SHA1 --stream" workflows: version: 2 porter-staging-workflow: jobs: - porter: context: ``` Make sure to replace `` and `` with your actual CircleCI context name and application name. # Alerts Source: https://docs.porter.run/applications/observability/alerts Set up Slack, email, and PagerDuty notification groups to receive alerts for crash loops, out-of-memory errors, and non-zero exit codes On critical events such as Application Crash Loop, Out of Memory errors, or non-zero exit codes, Porter will alert you on Slack. You can create `Notification Groups` by heading to `Settings` on your Porter project and setting up channels - you can set up email, Slack or PagerDuty. These can then be attached to apps(in any app on the Porter dashboard, head to the `Settings` tab to attach a notification group to that specific app). For advanced and custom alerts, we recommend using a 3rd party addon such as Datadog, Mezmo, or New Relic that you can easily install from the `Addons` tab. # App metadata environment variables Source: https://docs.porter.run/applications/observability/app-metadata Reference for default environment variables Porter injects into every app, including CPU, RAM, replicas, pod IP, image tag, and domains Porter injects some default environment variables in all Porter-provisioned apps, containing basic metadata around your app and the current deployment revision. A full list of these environment variables may be found here: 1. `PORTER_RESOURCES_RAM` - The amount of RAM assigned to the current service. 2. `PORTER_RESOURCES_CPU` - The number of vCPU cores assigned to the current service. 3. `PORTER_RESOURCES_REPLICAS` - The static replica count for the current service. Note that this may differ from the actual number of replicas running, if you have autoscaling enabled. 4. `PORTER_NODE_NAME` - The node the current service replica is running on. 5. `PORTER_NODE_IP` - The internal IP of the node the current service replica is running on. 6. `PORTER_POD_NAME` - This is the same as the internal hostname for the current service replica. 7. `PORTER_POD_IP` - The internal private IP assigned to the current service replica. 8. `PORTER_POD_IMAGE_TAG` - The image tag being used to run the current service replica. This is typically the same as `PORTER_IMAGE_TAG`. 9. `PORTER_IMAGE_TAG` - The image tag being used for the current app. This is typically the same as `PORTER_POD_IMAGE_TAG`. 10. `PORTER_POD_REVISION` - The revision ID assigned to the latest app deployment by Porter. 11. `PORTER_APP_SERVICE_NAME` - A portmanteau of the app name and service name: `-SERVICE`. 12. `PORTER_DOMAINS` - A comma-separated list of the domains assigned to your service. The following environment variables are only injected into [preview environments](/preview-environments/overview): 13. `PORTER_PR_BRANCH` - The git branch (head ref) of the pull request that created the preview environment. 14. `PORTER_PR_NUMBER` - The number of the pull request that created the preview environment. 15. `PORTER_PR_NAMESPACE` - The Kubernetes namespace of the preview environment. Use this when constructing in-cluster DNS names for services in the same preview, e.g. `.$PORTER_PR_NAMESPACE.svc.cluster.local`. Prefer this over `PORTER_PR_BRANCH`, which is the raw branch name and is not a valid DNS label for long or non-conforming branch names. If you're looking at adding more metadata to logs or traces, these environment variables can be used to inject metadata about the origin of a log/trace into your observability tooling. # Custom metrics autoscaling Source: https://docs.porter.run/applications/observability/custom-metrics-and-autoscaling Scale your web services based on custom Prometheus metrics by configuring metrics scraping endpoints and KEDA-powered autoscaling rules # Custom Metrics Autoscaling Porter supports autoscaling based on custom Prometheus metrics, allowing you to scale your services based on application-specific signals like queue length, request latency, or business metrics. For other autoscaling options, see the [Autoscaling overview](/applications/configure/autoscaling). ## Configuring Metrics Scraping **Note:** Metrics scraping is only available for web services. You can configure Porter to scrape metrics from your application's `/metrics` endpoint. This is useful for: * Collecting application-specific metrics * Setting up custom autoscaling based on your metrics * Monitoring application performance ### How to Enable Metrics Scraping 1. Navigate to your application dashboard 2. Select your web service 3. Go to the **Advanced** tab under service settings 4. Find the **Metrics scraping** section 5. Enable **Enable metrics scraping** 6. Configure the following options: * **Port**: The port where your metrics endpoint is exposed (defaults to your web service's default port) * **Path**: The path where metrics are exposed (defaults to `/metrics`) **Important:** Our telemetry collector will automatically send requests to the specified port and path to collect metrics from your service. Metrics Scraping Configuration *Metrics scraping configuration in the Advanced tab of a web service* ### Prometheus Metrics Format Your application must expose metrics in Prometheus format: * Metrics are exposed as HTTP endpoints (typically `/metrics`) * Each metric follows the format: `metric_name{label1="value1",label2="value2"} value` * Common metric types: * **Counter**: Values that only increase (e.g., `http_requests_total`) * **Gauge**: Values that can go up and down (e.g., `queue_length`) * **Histogram**: Observations distributed into buckets (e.g., `request_duration_seconds`) Example metrics output: ```prometheus theme={null} # HELP http_requests_total Total number of HTTP requests # TYPE http_requests_total counter http_requests_total{method="post",code="200"} 1027 http_requests_total{method="get",code="200"} 2048 ``` For detailed information about implementing Prometheus metrics in your application, refer to: * [Official Prometheus Exposition Format](https://prometheus.io/docs/instrumenting/exposition_formats/) * [Client Libraries for Different Languages](https://prometheus.io/docs/instrumenting/clientlibs/) ## Configuring Custom Autoscaling With metrics scraping enabled, you can set up autoscaling based on your custom metrics. ### How to Configure 1. Navigate to your application dashboard 2. Select your service 3. Go to the **Resources** tab 4. Configure basic autoscaling: * Enable **Autoscaling** * Set **Min instances** (e.g., 1) * Set **Max instances** (e.g., 10) 5. Switch to custom metrics mode by clicking the customize icon 6. Configure custom metrics: * **Metric Name**: Select a metric from your exposed Prometheus metrics * **Query**: Write or modify the PromQL query (defaults to `avg()`) * **Threshold**: Set the threshold value that triggers scaling When your selected metric exceeds the threshold, Porter will automatically scale your service between the min and max instances you've specified. Custom Autoscaling Configuration *Custom autoscaling configuration in the Resources tab of a service* ### Query Requirements Your PromQL query must return a single numeric value (scalar). **Valid query examples:** * `avg(metric_name)` → Returns a single average value * `sum(rate(http_requests_total[5m]))` → Returns a single sum value * `max(queue_length)` → Returns a single maximum value **Invalid queries:** * Vector results (multiple time series) * String results * No data/empty results If your query returns multiple values, use aggregation operators like `avg()`, `sum()`, or `max()` to reduce it to a single value. ### Switching Between Autoscaling Modes You can switch between: * **Default Mode**: Autoscale based on CPU/Memory usage * **Custom Mode**: Autoscale based on your application metrics Click the customize/restore icons to switch between modes. ## Example: Message Queue Consumer Consider a data processing pipeline with a web API and worker service: ### Analytics Ingestion API A web service that ingests events and publishes them to RabbitMQ for processing. ```python theme={null} from prometheus_client import generate_latest, Gauge # Track messages in RabbitMQ queues queue_metrics = Gauge('rabbitmq_queue_messages', 'Number of messages in queue', ['queue_name']) @app.route('/metrics') def metrics(): queue_metrics.labels(queue_name='user_events').set( rabbit_connection.get_queue_length('user_events')) queue_metrics.labels(queue_name='system_events').set( rabbit_connection.get_queue_length('system_events')) return generate_latest() ``` ### Event Processing Worker A worker service that processes events from RabbitMQ. **Custom Autoscaling Configuration:** * Metric Name: `rabbitmq_queue_messages{queue_name="user_events"}` * Query: `sum(rabbitmq_queue_messages{queue_name="user_events"})` * Threshold: `1000` (scale up when more than 1000 events are waiting) With this setup, Porter will add more workers when the queue backs up and scale down when the queue is processed. # DevOps Agent Source: https://docs.porter.run/applications/observability/devops-agent Use the AI-powered DevOps Agent in the Porter dashboard to debug application issues with read-only access to your cluster in real time The DevOps Agent is currently in alpha. Features and behavior may change as we iterate on feedback. Responses are AI-generated and may not always be accurate. If you're unsure about a recommendation, reach out to support. The DevOps Agent is an AI-powered debugging assistant built into the Porter dashboard. When something goes wrong with your app, you can open the agent sidebar and ask it what's happening in plain English. It has read-only access to your cluster, so it can investigate your infrastructure in real time without being able to modify anything. It translates what it finds into actionable answers and links you to the right docs or settings to fix the problem. ## Setup To enable the DevOps Agent, click **DevOps Agent** in the sidebar or press `⌘+I`. If the agent hasn't been configured yet, you'll see a setup prompt. Click **Configure** to open the settings panel. DevOps Agent setup prompt You'll need to configure a few things: 1. **Provider** - either Anthropic (direct API) or AWS Bedrock 2. **Model** - which model to run (Claude Sonnet 4.6, Claude Opus 4.6, or Claude Haiku 4.5) 3. **API key** - your API key for the selected provider 4. **Provider URL** (Bedrock only) - your Bedrock runtime endpoint, formatted as `https://bedrock-runtime.{region}.amazonaws.com` (see [AWS Bedrock endpoints](https://docs.aws.amazon.com/general/latest/gr/bedrock.html) for available regions) AWS Bedrock currently only supports long-term API keys generated from the Bedrock console. Short-term generated credentials are not supported. Click **Save changes** to deploy the agent to your cluster. DevOps Agent settings panel It takes a few seconds for the agent to start up. You'll see a loading indicator while it deploys. DevOps Agent starting up The agent is configured per cluster, so you only need to set it up once per cluster. You can disable the agent at any time from the same settings panel. Disabling removes the agent from your cluster and clears the configuration. ## How It Works The agent runs inside your cluster and has read-only access to inspect the infrastructure backing your apps, add-ons, and datastores. When you ask a question, it pulls together information from multiple sources to build a diagnosis: * **Porter API** - deployment history, revision status, service configuration, build info * **Live cluster state** - service logs, resource usage, events, scheduling status * **Porter docs and knowledge base** - troubleshooting guides and configuration references The agent is context-aware. It knows which app, add-on, or datastore you're currently viewing in the dashboard and scopes its investigation to that resource. You don't need to tell it where to look. ## Using the Agent Open the DevOps Agent with `⌘+I` (or `Ctrl+I` on Windows/Linux), or click the agent icon in the sidebar. It's available on any app, add-on, datastore, or cluster page. Ask your question in the input field and the agent will start investigating. As it works, you can see what it's checking in a collapsible thinking section. Agent investigating a question Once it has a diagnosis, it presents a concise answer with what went wrong and how to fix it, usually with links to the relevant Porter docs or dashboard settings. The agent maintains a session, so you can ask follow-up questions to dig deeper into the same issue. ### Stopping a response If you want to stop the agent while it's responding, click the **stop** button that appears in place of the send button, or press `Escape` while the input field is focused. The agent will stop its current response and keep everything it has generated so far, including any thinking steps. Your session stays active, so you can send a new message or follow-up question right away without starting over. If you send a message while the agent is still responding, it gets queued and will be delivered automatically after the current response finishes or is stopped. After the agent responds, you can rate the answer with thumbs up or thumbs down. If the answer wasn't helpful, you'll be prompted to select a reason (inaccurate, not helpful, or too verbose). You can also create a support ticket directly from any agent response by clicking the headset icon, which is useful if the agent's diagnosis points to something that needs human help. Response with feedback actions ## What You Can Ask About The agent is good at diagnosing runtime issues with your apps and infrastructure. Some common questions: * Why is my service restarting? * Why did this deployment fail? * What's using all the memory on my cluster? * Why can't my app connect to the database? * How do I set up autoscaling for this service? * Why is my build failing? It can also answer general "how do I..." questions about Porter by searching the docs and knowledge base. ## Limitations The agent has read-only access. It can inspect your infrastructure and tell you what's wrong, but it can't make changes on your behalf. Any fixes it recommends will point you to the right place in the dashboard, CLI, or `porter.yaml` to make the change yourself. The agent is scoped to the resource you're viewing. If you need help with a different app or cluster, navigate to that resource's page and start a new session. ## FAQ ### Does the DevOps Agent cost anything? The agent itself is free to use on Porter. However, each query uses your own LLM API key, so you'll be billed by your provider (e.g. Anthropic) for the tokens consumed. A typical debugging session uses a moderate number of tokens across the tool calls and response. ### Can the agent affect my production workloads? No. The agent runs as a small, isolated container on your cluster with minimal resource requirements (50m CPU request, 128Mi memory request). It has read-only access to your cluster and can't modify or interfere with your services. # Logging Source: https://docs.porter.run/applications/observability/logging Search and browse application logs retained for up to 7 days, stored securely in your own cluster infrastructure using Grafana Loki Porter retains your logs up to 7 days by default. You can perform basic search across your logs and navigate across different time periods. Logging Tab All logs are stored exclusively inside your own infrastructure using [Loki](https://github.com/grafana/loki) and does not get stored anywhere on Porter's system. Porter is responsible for the reliability of the Loki instance running on your cluster. # Monitoring Source: https://docs.porter.run/applications/observability/monitoring View CPU, RAM, network usage, and throughput metrics for your applications with 14-day retention using Prometheus inside your own cluster Porter provides observability around your basic application metrics. Below are the metrics that Porter supports: * CPU usage * RAM usage * Network Usage * Network Throughput (number of networking errors from NGINX) All metrics are retained up to 14 days by default and are collected via [Prometheus](https://prometheus.io/) that runs inside your own cluster. Porter is responsible for the reliability of this prometheus instance and manages it on an ongoing basis. While these metrics support basic use cases, for advanced options we recommend using a 3rd party addon such as DataDog, Mezmo, or New Relic. Installing these addons is as simple as one click. You can find a list of 3rd party observability tools that Porter supports inside the **Add-ons** tab. # CLI basic usage Source: https://docs.porter.run/cli/basic-usage Authenticate, configure projects and clusters, and deploy applications using essential Porter CLI commands including apply, config, and app This guide covers the essential commands and configuration options for the Porter CLI. ## Quick Start Authenticate with your Porter account: ```bash theme={null} porter auth login ``` This opens your browser to complete authentication and automatically configures your default project and cluster. Check your current CLI configuration: ```bash theme={null} porter config ``` If necessary, [switch your project and/or cluster](#project-and-cluster-configuration) Deploy using a `porter.yaml` file: ```bash theme={null} porter apply -f porter.yaml ``` [`porter.yaml` reference](/applications/configuration-as-code/overview) [`porter apply` reference](/standard/cli/command-reference/porter-apply) ## Project and Cluster Configuration After logging in, you may need to switch between projects or clusters. ### List Available Projects ```bash theme={null} porter projects list ``` ### Set Active Project ```bash theme={null} porter config set-project [PROJECT_ID] ``` ### List Available Clusters ```bash theme={null} porter clusters list ``` ### Set Active Cluster ```bash theme={null} porter config set-cluster [CLUSTER_ID] ``` ### View Current Configuration ```bash theme={null} porter config ``` ## Global Flags These flags can be used with any Porter command: | Flag | Description | | ------------------ | ---------------------------------------- | | `--project ` | Override the project ID for this command | | `--cluster ` | Override the cluster ID for this command | | `--token ` | Use a specific authentication token | | `-h, --help` | Display help for the command | ```bash Override Project theme={null} porter app logs my-app --project 12345 ``` ```bash Override Cluster theme={null} porter app run my-app --cluster 67890 -- bash ``` ## Environment Variables Environment variables provide an alternative way to configure the CLI, which is especially useful in CI/CD pipelines. | Variable | Description | Equivalent Flag | | ----------------- | ----------------------------- | --------------- | | `PORTER_PROJECT` | Project ID to use | `--project` | | `PORTER_CLUSTER` | Cluster ID to use | `--cluster` | | `PORTER_TOKEN` | Authentication token | `--token` | | `PORTER_HOST` | Custom Porter API host | `--host` | | `PORTER_APP_NAME` | Default app name for commands | `--app` | Environment variables take precedence over values in your config file, but flags take precedence over environment variables. ### Example: CI/CD Configuration ```bash theme={null} export PORTER_TOKEN="your-deploy-token" export PORTER_PROJECT="12345" export PORTER_CLUSTER="67890" porter apply -f porter.yaml ``` ## Common Workflows ### Local Development ```bash theme={null} # Login and configure porter auth login # View your app's logs porter app logs my-app # Run a command in an ephemeral copy of your app porter app run my-app -- bash # View current app configuration porter app yaml my-app ``` ### CI/CD Deployment ```bash theme={null} # Deploy with explicit configuration PORTER_TOKEN=$DEPLOY_TOKEN \ PORTER_PROJECT=$PROJECT_ID \ PORTER_CLUSTER=$CLUSTER_ID \ porter apply -f porter.yaml ``` ### Managing Environment Variables ```bash theme={null} # Pull environment variables from an app porter env pull -a my-app # Pull environment variables from an environment group porter env pull -g my-env-group # Set environment variables on an app porter env set -a my-app -v KEY=value # Set secrets on an environment group porter env set -g my-env-group -s API_KEY=secret123 ``` ### Debugging ```bash theme={null} # Stream live logs porter app logs my-app # View historical logs porter app logs my-app --since 1h ``` ## Viewing Help You can view help instructions for any command using the `-h` or `--help` flag: ```bash theme={null} # General help porter -h # Help for a specific command porter app -h # Help for a subcommand porter app run -h ``` ## Next Steps * Learn about [configuration-as-code](/applications/configuration-as-code/overview) with `porter.yaml` * Explore the [full command reference](/standard/cli/command-reference/porter-apply) for detailed options * Set up [CI/CD deployment](/applications/deploy/using-other-ci-tools) for automated deployments # CLI installation Source: https://docs.porter.run/cli/installation Install the Porter CLI on macOS via Homebrew or shell script, on Linux via shell script, and on Windows via manual binary download To install the Porter CLI, see the OS-specific instructions below. Working with an AI agent? `curl -fsSL https://agents.porter.run | sh` installs the CLI *and* registers the [Porter MCP server](/mcp/overview) with your agent client, plus Porter's skills. #### Install via Homebrew ```bash theme={null} brew install porter-dev/porter/porter ``` #### Manual Installation **Prerequisites:** The installation process requires `curl` and `unzip` utilities. ```bash theme={null} /bin/bash -c "$(curl -fsSL https://install.porter.run)" ``` **Prerequisites:** The installation process requires `curl` and `unzip` utilities. ```bash theme={null} /bin/bash -c "$(curl -fsSL https://install.porter.run)" ``` After installing, run the following to verify your installation: ```bash theme={null} porter version ``` # Advanced Cluster Settings Source: https://docs.porter.run/cloud-accounts/advanced-cluster-settings Enable ECR scanning, GuardDuty, private clusters, KMS encryption, and custom networking options for your AWS, GCP, and Azure Porter clusters Porter exposes advanced cluster configuration options for customers with specific compliance, security, or networking requirements. These settings are available on the **Advanced** tab of your cluster settings for **AWS**, **GCP**, and **Azure** clusters. ## Networking ### Private cluster When **Private cluster** is enabled, Porter provisions the EKS cluster with **both public and private** API server endpoint access, and restricts the public endpoint to an IP allowlist containing Porter's [control-plane IPs](/security-and-compliance/porter-ip-ranges) plus any customer CIDRs you add. This configuration is SOC2 / HIPAA compliant. | Setting | Description | | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Private cluster** | Restricts the EKS API server's public endpoint to an IP allowlist (Porter's IPs plus any customer-supplied CIDRs). The private endpoint inside your VPC remains reachable from VPC-attached resources. | | **CIDR allowlist** | Additional CIDR ranges (beyond Porter's required IPs) that may reach the public endpoint. | Porter intentionally does **not** enable EKS "private-only" endpoint mode. Private-only forces every control-plane call — including Porter's — through a VPN or VPC-peered path, which adds operational complexity and has historically caused outages for customers. Public + private with a tight IP allowlist meets the same compliance requirements and is significantly more reliable. The [Tailscale integration](/security-and-compliance/tailscale) is a separate layer that carries traffic for `porter kubectl` and `porter helm` commands; it does not control how the EKS API server endpoint itself is exposed. ### Load balancer Configure the type of load balancer used for your cluster's ingress. Changing this setting causes downtime while the load balancer is recreated. | Type | Description | | ------- | --------------------------------------------------------------------------------------------------------------------------------------- | | **NLB** | Network Load Balancer — operates at Layer 4, routes TCP/UDP traffic, and provides ultra-low latency and high throughput. | | **ALB** | Application Load Balancer — operates at Layer 7, routes HTTP/HTTPS traffic, and supports wildcard domains, ACM certificates, and WAFv2. | When **ALB** is selected, the following additional settings become available. See [Custom domains with ALB](/applications/configure/custom-domains-alb) for end-to-end setup instructions. | Setting | Description | | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Wildcard domains** | Domains to issue ACM certificates for. Do not include the `*.` prefix — Porter creates a SAN for `*.` automatically. Comma-separate multiple domains. | | **IP allow list** | Comma-separated list of CIDR ranges (e.g. `160.72.72.58/32,160.72.72.59/32`) permitted to reach the ALB. | | **Certificate ARNs** | Existing ACM certificate ARNs to attach to the ALB. | | **AWS tags** | Key/value tags applied to the ALB and related AWS resources. | | **WAFv2 enabled** | Attaches a Regional WAFv2 web ACL to the ALB. | | **WAFv2 ARN** | ARN of the Regional WAFv2 web ACL to attach. Only Regional WAFv2 is supported. | ### Private load balancer In addition to the default public cluster load balancer, you can provision an **internal load balancer** that only accepts traffic from inside your VPC (or networks peered to it). Use this when you want to expose services to internal clients, for example an internal admin tool, a service consumed only by other VPCs, or a workload that must not be reachable from the public internet. | Setting | Description | | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | **Add private load balancer** | Provisions a private NLB alongside the existing public cluster load balancer. Only NLB private load balancers are supported. | Once enabled, you must configure a DNS provider so Porter can issue and renew TLS certificates for ingress hostnames attached to the private load balancer. The following DNS providers are supported: **Cloudflare** and **AWS Route53**. We recommend serving private ingress from a dedicated internal zone (for example, `internal.example.com`) rather than a zone that also serves production domains. This avoids record conflicts with production DNS and keeps DNS access scoped to internal hostnames only. If that isn't practical, you can still keep credentials off your production zone. See [Delegating certificate validation](#delegating-certificate-validation) below. | Setting | Description | | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | **DNS credentials** | API token for Cloudflare. The token must have permission to create and delete `TXT` records on the zones used by your private ingress hostnames. | Save the credentials before updating the cluster. You can rotate the token later with **Edit credentials**, or remove the integration entirely with **Remove**. Removing credentials stops certificate issuance and renewal for private load balancer ingress. When the cluster is on AWS, you can use Route53 instead of Cloudflare. Porter authenticates to Route53 through an [EKS Pod Identity](https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html) scoped to a single hosted zone, so no API tokens are stored. | Setting | Description | | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Route53 domain** | The DNS zone the private ingress serves certificates for (for example, `internal.example.com`). At install time, Porter resolves the matching public Route53 hosted zone in the same AWS account and provisions a cert-manager pod identity scoped to that zone. | The domain must already exist as a public hosted zone in the cluster's AWS account, and the zone name must match the domain exactly. If only a parent zone is present (for example, `example.com` for a domain of `internal.example.com`), Porter cannot issue certificates for it, and the domain requires its own dedicated hosted zone. #### Delegating certificate validation By default, the DNS credentials you give Porter need write access to the zone that hosts your private hostnames. If those hostnames live under a zone that also serves production domains, those credentials can reach your production records too, which is more privilege than you may want to grant. You can avoid that by delegating just the validation records to a separate, lower-privilege zone. You add a `CNAME` from each validation record in your zone to the delegated zone, then scope the credentials to that zone only. Porter handles the rest with no extra configuration on its side. On AWS Route53 this isn't necessary. Create a dedicated hosted zone for the subdomain and provide it as the **Route53 domain** (the Route53 option above); Porter scopes access to that zone directly. **1. Add the delegation `CNAME` records.** Pick a low-privilege zone to hold the validation records (for example, `acme.example-internal.com`). In the zone that hosts your private hostnames, add a `CNAME` for each validation record, pointing at a record in the delegated zone: ``` _acme-challenge.app.internal.example.com. CNAME _acme-challenge.acme.example-internal.com. ``` **2. Scope the credentials to the delegated zone.** Create an API token scoped to the **delegated zone only**, with: * `Zone.DNS` → Edit * `Zone.Zone` → Read The token does not need any access to your production zone. **3. Verify the delegation.** Confirm the `CNAME` resolves publicly: ```bash theme={null} dig +short CNAME _acme-challenge.app.internal.example.com # expected: _acme-challenge.acme.example-internal.com. ``` Certificates are issued automatically when you attach a hostname to a service through its [load balancer config](/applications/configuration-as-code/services/web-service#loadbalancerconfig). Porter then publishes each validation record into the delegated zone through the `CNAME`. If you need help setting this up, reach out to Porter support. ## Observability ### CloudWatch control plane logs Configure which EKS cluster control plane log types are sent to AWS CloudWatch. These logs help with debugging, auditing, and monitoring your cluster's control plane components. | Log Type | Description | | --------------------------- | ------------------------------------------------------------------------------------------------ | | **API Server logs** | Logs from the Kubernetes API server, useful for debugging API requests | | **Audit logs** | Records of individual users, administrators, or system components that have affected the cluster | | **Authenticator logs** | Logs from the AWS IAM authenticator, useful for debugging authentication issues | | **Controller manager logs** | Logs from the controller manager, which manages core control loops | | **Scheduler logs** | Logs from the scheduler, useful for debugging pod scheduling decisions | ### CloudWatch Observability agent You may also enable the CloudWatch Observability agent as an EKS add-on for enhanced cluster monitoring. | Setting | Description | | ----------------------------------------------------------- | --------------------------------------------------------------------------- | | **AWS CloudWatch Observability agent installed on cluster** | Enables the CloudWatch Observability add-on for metrics and logs collection | ## Security ### ECR scanning Enable Amazon ECR image scanning to automatically scan container images for software vulnerabilities. | Setting | Description | | ------------------------ | -------------------------------------------------------------------------------- | | **ECR scanning enabled** | When enabled, images pushed to ECR are automatically scanned for vulnerabilities | ### AWS GuardDuty AWS GuardDuty provides intelligent threat detection for your EKS cluster, monitoring for malicious activity and unauthorized behavior. When enabling GuardDuty, you must also configure the following in your AWS Console: 1. Enable EKS Protection in the EKS Protection tab of the GuardDuty console 2. Enable Runtime Monitoring For automated agent configuration, enable both: * EKS agent auto-configuration * EC2 agent auto-configuration | Setting | Description | | -------------------------------------------- | ----------------------------------------------------------- | | **AWS GuardDuty agent installed on cluster** | Installs the GuardDuty security agent on your cluster nodes | ### KMS encryption Enable AWS Key Management Service (KMS) encryption for Kubernetes secrets stored in etcd. | Setting | Description | | -------------------------- | -------------------------------------------------------------------- | | **KMS encryption enabled** | Encrypts Kubernetes secrets at rest using a customer-managed KMS key | ## Advanced Networking Config Modifying these advanced network settings can impact cluster connectivity and performance. Ensure you understand the implications before making changes. | Setting | Description | Default | | --------------------------------------- | ------------------------------------------------------------------- | -------- | | **Egress NAT IPs Count** | Number of egress NAT IPs. Cannot be decreased once set. | 1 | | **Min Ports per VM** | Configures the minimum number of ports allocated per VM for GKE | 64 | | **Enable Endpoint Independent Mapping** | Recommended for most use cases. Affects Cloud NAT behavior. | Enabled | | **Enable Dynamic Port Allocation** | Allows GKE to dynamically allocate more ports to VMs that need them | Disabled | ## Networking ### Private load balancer In addition to the default public cluster load balancer, you can provision an **internal load balancer** on GKE that only accepts traffic from inside your VPC (or networks peered to it). Use this when you want to expose services to internal clients, for example an internal admin tool, a service consumed only by other VPCs, or a workload that must not be reachable from the public internet. | Setting | Description | | ----------------------------- | -------------------------------------------------------------------------------------------- | | **Add private load balancer** | Provisions a GKE internal load balancer alongside the existing public cluster load balancer. | Once enabled, you must configure a DNS provider so Porter can issue and renew TLS certificates for ingress hostnames attached to the private load balancer. **Cloudflare** is supported as a DNS provider for private load balancer ingress. We recommend serving private ingress from a dedicated internal zone (for example, `internal.example.com`) rather than a zone that also serves production domains. This avoids record conflicts with production DNS and keeps DNS access scoped to internal hostnames only. If that isn't practical, you can still keep credentials off your production zone. See [Delegating certificate validation](#delegating-certificate-validation-1) below. | Setting | Description | | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | **DNS credentials** | API token for Cloudflare. The token must have permission to create and delete `TXT` records on the zones used by your private ingress hostnames. | Save the credentials before updating the cluster. You can rotate the token later with **Edit credentials**, or remove the integration entirely with **Remove**. Removing credentials stops certificate issuance and renewal for private load balancer ingress. #### Delegating certificate validation By default, the DNS credentials you give Porter need write access to the zone that hosts your private hostnames. If those hostnames live under a zone that also serves production domains, those credentials can reach your production records too, which is more privilege than you may want to grant. You can avoid that by delegating just the validation records to a separate, lower-privilege zone. You add a `CNAME` from each validation record in your zone to the delegated zone, then scope the credentials to that zone only. Porter handles the rest with no extra configuration on its side. **1. Add the delegation `CNAME` records.** Pick a low-privilege zone to hold the validation records (for example, `acme.example-internal.com`). In the zone that hosts your private hostnames, add a `CNAME` for each validation record, pointing at a record in the delegated zone: ``` _acme-challenge.app.internal.example.com. CNAME _acme-challenge.acme.example-internal.com. ``` **2. Scope the credentials to the delegated zone.** Create an API token scoped to the **delegated zone only**, with: * `Zone.DNS` → Edit * `Zone.Zone` → Read The token does not need any access to your production zone. **3. Verify the delegation.** Confirm the `CNAME` resolves publicly: ```bash theme={null} dig +short CNAME _acme-challenge.app.internal.example.com # expected: _acme-challenge.acme.example-internal.com. ``` Certificates are issued automatically when you attach a hostname to a service through its [load balancer config](/applications/configuration-as-code/services/web-service#loadbalancerconfig). Porter then publishes each validation record into the delegated zone through the `CNAME`. If you need help setting this up, reach out to Porter support. ## Observability Settings Configure observability features for the GKE cluster control plane. | Setting | Description | | -------------------------------- | -------------------------------------------------------------------------------------------------------- | | **Enable Control Plane Logging** | Enable the collection of logs from the Kubernetes control plane components (e.g., API server, scheduler) | | **Enable Control Plane Metrics** | Enable the collection of metrics from the Kubernetes control plane components | ## Networking ### Private load balancer In addition to the default public cluster load balancer, you can provision an **internal load balancer** on AKS that only accepts traffic from inside your VNet (or networks peered to it). Use this when you want to expose services to internal clients, for example an internal admin tool, a service consumed only by other networks, or a workload that must not be reachable from the public internet. | Setting | Description | | ----------------------------- | ----------------------------------------------------------------------------------------------- | | **Add private load balancer** | Provisions an internal Azure load balancer alongside the existing public cluster load balancer. | Once enabled, you must configure a DNS provider so Porter can issue and renew TLS certificates for ingress hostnames attached to the private load balancer. **Cloudflare** is supported as a DNS provider for private load balancer ingress. We recommend serving private ingress from a dedicated internal zone (for example, `internal.example.com`) rather than a zone that also serves production domains. This avoids record conflicts with production DNS and keeps DNS access scoped to internal hostnames only. If that isn't practical, you can still keep credentials off your production zone. See [Delegating certificate validation](#delegating-certificate-validation-2) below. | Setting | Description | | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | **DNS credentials** | API token for Cloudflare. The token must have permission to create and delete `TXT` records on the zones used by your private ingress hostnames. | Save the credentials before updating the cluster. You can rotate the token later with **Edit credentials**, or remove the integration entirely with **Remove**. Removing credentials stops certificate issuance and renewal for private load balancer ingress. #### Delegating certificate validation By default, the DNS credentials you give Porter need write access to the zone that hosts your private hostnames. If those hostnames live under a zone that also serves production domains, those credentials can reach your production records too, which is more privilege than you may want to grant. You can avoid that by delegating just the validation records to a separate, lower-privilege zone. You add a `CNAME` from each validation record in your zone to the delegated zone, then scope the credentials to that zone only. Porter handles the rest with no extra configuration on its side. **1. Add the delegation `CNAME` records.** Pick a low-privilege zone to hold the validation records (for example, `acme.example-internal.com`). In the zone that hosts your private hostnames, add a `CNAME` for each validation record, pointing at a record in the delegated zone: ``` _acme-challenge.app.internal.example.com. CNAME _acme-challenge.acme.example-internal.com. ``` **2. Scope the credentials to the delegated zone.** Create an API token scoped to the **delegated zone only**, with: * `Zone.DNS` → Edit * `Zone.Zone` → Read The token does not need any access to your production zone. **3. Verify the delegation.** Confirm the `CNAME` resolves publicly: ```bash theme={null} dig +short CNAME _acme-challenge.app.internal.example.com # expected: _acme-challenge.acme.example-internal.com. ``` Certificates are issued automatically when you attach a hostname to a service through its [load balancer config](/applications/configuration-as-code/services/web-service#loadbalancerconfig). Porter then publishes each validation record into the delegated zone through the `CNAME`. If you need help setting this up, reach out to Porter support. # Cluster observability Source: https://docs.porter.run/cloud-accounts/cluster-observability Monitor pod status, node resource usage, and cluster-wide infrastructure metrics in real time from the Porter Infrastructure dashboard Porter provides built-in observability for your cluster infrastructure through the **Infrastructure** dashboard. Access it by clicking **Infrastructure** in the left sidebar. *** ## Pods The **Pods** tab provides a real-time view of all pods running in your cluster. * **Search**: Filter pods by name * **Filters**: Filter by status or namespace Each pod displays: | Column | Description | | ------------- | ----------------------------------------------------- | | **Pod name** | The name of the pod | | **Namespace** | Kubernetes namespace (e.g., `kube-system`, `default`) | | **Status** | Current state (Running, Pending, Failed, etc.) | | **Ready** | Container readiness (e.g., `1/1`) | | **Restarts** | Number of container restarts | | **CPU** | CPU usage | | **Memory** | Memory usage | | **Memory %** | Percentage of memory limit used | | **Age** | Time since pod creation | *** ## Nodes The **Nodes** tab shows your cluster's node groups and individual nodes. ### Node Groups View The default view displays all node groups: | Column | Description | | ----------------- | ---------------------------------------------------------- | | **Node group** | Name of the node group (e.g., default, monitoring, system) | | **Instance type** | The machine type for nodes in this group | | **Utilization** | Visual indicator of resource usage | | **Actions** | Link to view detailed metrics | ### Individual Nodes View Click on a node group to see individual nodes: * **Node name**: The cloud provider's node identifier * **Node group**: Which node group this node belongs to * **Instance type**: The machine type * **CPU**: CPU utilization shown as utilized (yellow) vs reserved (blue) * **Memory**: Memory utilization shown as utilized (yellow) vs reserved (blue) * **Status**: Node health status (Ready, NotReady) Click **Metrics >** on any node group to view historical instance counts over time. *** ## Integrating External Monitoring For application-level monitoring and alerting, integrate with external observability platforms: Full-stack monitoring with APM, logs, and infrastructure metrics Application performance monitoring and alerting Dashboards and visualization for metrics and logs See [Third party observability](/addons/third-party-observability) or reach out to support for more information. # Cluster upgrades Source: https://docs.porter.run/cloud-accounts/cluster-upgrades How Porter manages automated Kubernetes version upgrades for your cluster, including the shared responsibility model and prerequisites Keeping your Kubernetes clusters up-to-date is essential for ensuring security, stability, and access to the latest features built by the wider Kubernetes community as well as the underlying public cloud. To that end, we take care of managed Kubernetes upgrades for all clusters provisioned through our platform. Our automated upgrade process ensures your clusters remain current without disrupting your workloads, so you can focus on building and deploying your applications while we handle the complexities of cluster maintenance. ## Shared Responsibility Model We've endeavoured to build a world-class cluster management system which is able to manage and upgrade customer infrastructure without causing disruption to customer workloads. To that end, we've defined a shared responsibility model which maps out the roles played by Porter's engineering/SRE teams as well as customers to ensure the best possible experience with upgrades. ### Customers' responsibilities 1. Ensuring all production workloads are running with [a minimum of 3 replicas](/applications/configure/zero-downtime-deployments#high-availability-applications). 2. Adding [functional healthchecks](/applications/configure/zero-downtime-deployments#health-checks) to all production workloads. 3. Adding [support for graceful shutdowns](/applications/configure/zero-downtime-deployments#graceful-shutdown) to all production workloads. If your app doesn't have a minimum of three replicas / doesn't use healthchecks or graceful shutdowns, then an upgrade can cause significant disruption. During an upgrade, your cluster's nodes are refreshed and workloads are gradually rescheduled and the absence of these prerequisites will lead to a scenario where your cluster may not have ready replicas to serve production traffic. More documentation around zero-downtime deployments may be found [here](/applications/configure/zero-downtime-deployments). ### Porter's responsibilities 1. Testing each upgrade release extensively to ensure new versions and system components work together cohesively. 2. Executing upgrades in a blue-green deployment fashion, where older components and nodes are gradually replaced with newer versions without compromising customer workload uptime. 3. Maintaining a constant stream of communication around upgrade timelines and statuses. ## Upgrade Calendar Kubernetes follows a release cycle where there are - approximately - three minor version releases a year. Every release is followed by a period where public clouds integrate the new version into their managed Kubernetes offerings and run tests to ensure compatibility with the underlying cloud. Our upgrade calendar is thus dependent on both release cycles. To account for that, we carry out cluster upgrades twice a year, where we "leapfrog" over versions to ensure customer clusters are running the *latest stable* version of Kubernetes. These are typically carried out once towards the end of Q1/beginning of Q2 and then later towards the end of Q3. ## Upgrade Path When a new version of upstream Kubernetes is released, we closely track the corresponding release on public clouds in conjunction with the wider community as well as our public cloud partners (AWS, Google Cloud, Azure). 1. Whilst waiting for public clouds to launch the new version on their managed versions, we start conducting initial compatibility tests with upstream Kubernetes against our systems, to catch any potential issues. 2. Once public clouds have announced support for the new version, more extensive tests are conducted around the following themes: a. System components responsible for managing your cluster's functionality - Ingress, certificate management, autoscaling, telemetry and so on. All system components are upgraded to the latest stable versions from their upstream repos, and validated against both the current stable version and the new version of Kubernetes. b. App templates powering customer workloads for web, worker and job services. c. Additional addons - these include telemetry addons like Datadog/New Relic. 3. After our tests are successful, we announce a timeline for upgrades over our comms channels. At this point, while we typically announce a window during low-traffic hours when upgrades are conducted, customers have the option of scheduling a specific slot. 4. When a cluster is upgraded, we upgrade system components, all app templates, the managed cluster control plane as well as all nodegroups. While this operation is meant to be non-disruptive, there are certain prerequisites on the customers' end to ensure zero downtime (see the section below for more details). ## Node Patches Cluster nodes are typically a shared responsibility between Porter and the underlying cloud provider. Patches usually fall into two categories: 1. Regular machine image patches issued by the underlying cloud provider. These are typically applied automatically by the cloud provider every few weeks, although this can be manually overriden by running a simple update on your cluster; at that point if a fresh machine image is present in the underlying cloud provider's image stores, every node will be refreshed. 2. One-off patches pushed by Porter, in case of high / critical CVEs. In these cases, the underlying cloud provider releases a patched machine image, and Porter ensures it's deployed to every cluster. # Connecting a cloud account Source: https://docs.porter.run/cloud-accounts/connecting-a-cloud-account Grant Porter access to your AWS, GCP, or Azure account using IAM role assumption or Workload Identity Federation to provision infrastructure Before Porter can create a cluster, you need to grant it access to your cloud account. Porter uses secure credential methods that don't require storing static API keys. Porter uses AWS IAM role assumption via the `AssumeRole` [operation](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use.html) to access your account. You create a role in your AWS account and declare that you trust Porter to assume it. This eliminates static credentials and makes access easy to revoke. ## Create the IAM Role After selecting AWS as your cloud provider, log into your [AWS Console](https://console.aws.amazon.com) and find your 12-digit Account ID in the top-right corner. Enter this ID in Porter and click **Grant Permissions**. Porter opens the AWS CloudFormation console in a new tab to create a stack that provisions the `porter-manager` IAM role. If the popup is blocked, check your browser settings and allow popups from Porter. Scroll to the bottom of the CloudFormation page, check the **I acknowledge that AWS CloudFormation might create IAM resources** box, and click **Create Stack**. Wait for the stack creation to complete (this takes a few minutes). The IAM role must remain in your AWS account for Porter to manage your infrastructure. Deleting it will prevent Porter from making changes. ## Permissions Granted The CloudFormation stack creates an IAM role with permissions to: * Create and manage EKS clusters * Create and manage VPCs, subnets, and security groups * Create and manage ECR repositories * Create and manage IAM roles for cluster operations * Request service quota increases If you need Porter to operate with more restricted permissions, contact us through the support widget to inquire about Porter Enterprise. ## Revoking Access To revoke Porter's access: 1. First, delete any clusters through the Porter dashboard 2. Navigate to **CloudFormation Stacks** in your AWS console 3. Select the stack named `PorterRole` and click **Delete** This removes the IAM role and prevents Porter from accessing your account. Porter connects to GCP using [Workload Identity Federation](https://cloud.google.com/iam/docs/workload-identity-federation). Workload Identity Federation offers a mechanism for Porter to connect with your GCP project without requiring any static keys. Setup runs in [Google Cloud Shell](https://cloud.google.com/shell) with a single command Porter generates for your project. ## Prerequisites Your GCP project needs an active [billing account](https://console.cloud.google.com/billing) attached. Porter cannot provision infrastructure without one. The Google account running the setup must have these project-level roles (or `roles/owner`, which covers all of them): * `roles/serviceusage.serviceUsageAdmin` * `roles/iam.serviceAccountAdmin` * `roles/resourcemanager.projectIamAdmin` * `roles/iam.workloadIdentityPoolAdmin` * `roles/storage.admin` ## Connect Your GCP Project In Porter, select GCP. Enter your [GCP project ID](https://console.cloud.google.com) (visible at the top of any page in the GCP Console) and click **Connect**. Porter generates a one-time setup command scoped to this integration. In the Porter dashboard: 1. Copy the command shown in the **Run Setup in Cloud Shell** panel. 2. Click **Open Cloud Shell** to launch a new tab with the [porter-dev/gcp-onboarding](https://github.com/porter-dev/gcp-onboarding) repository pre-loaded. 3. Cloud Shell will warn that the repository is from an untrusted source. Click **Trust** to continue. The repository is open source so you can review the Terraform before approving. 4. Paste the command into the shell and press Enter. The script runs Terraform in your project and performs the initial setup: * Enables five Google APIs: Cloud Resource Manager, IAM, IAM Credentials, STS, and Service Usage * Creates a `porter-manager-*` service account that Porter impersonates * Creates a `porter-pool-*` Workload Identity Pool with a trust policy scoped to your project * Grants three bootstrap IAM roles to the service account so Porter can finish the rest of the configuration itself: `roles/serviceusage.serviceUsageAdmin`, `roles/resourcemanager.projectIamAdmin`, and `roles/iam.serviceAccountAdmin` Setup takes about 30 seconds. Porter's dashboard polls the connection automatically. Once the bootstrap completes, Porter takes over the heavier configuration on your behalf. It enables the remaining APIs needed for cluster provisioning (Compute, Kubernetes Engine, Artifact Registry, Secret Manager, and others) and grants the matching per-service roles to the `porter-manager-*` service account (Compute Admin, Kubernetes Engine Admin, Artifact Registry Admin, Secret Manager Admin, and others). Once everything is provisioned, the cloud account is marked connected and the status banner turns green. The full Terraform module is open source if you want to inspect every resource Porter creates: [porter-dev/gcp-onboarding](https://github.com/porter-dev/gcp-onboarding). ## Porter-Managed Infrastructure Once your project is connected, Porter provisions and continuously reconciles infrastructure in it: the VPC, Cloud NAT and Cloud Router, the GKE cluster, its node pools, and the cluster load balancer. Do not modify Porter-managed resources directly in the GCP Console or with `gcloud`. Out-of-band changes are detected as drift on the next cluster update: Porter will revert them, which can itself disrupt traffic. Changes to settings that GKE cannot update in place may cause the **cluster to be replaced entirely**, resulting in downtime and the loss of any manually created in-cluster resources. Settings that can trigger reconciliation or cluster replacement when changed outside of Porter include: * **Cloud NAT and Cloud Router** — port allocation (min ports per VM, dynamic port allocation), endpoint-independent mapping, TCP timeouts, NAT IP allocation mode and static NAT IPs, source subnetwork ranges * **VPC** — the cluster VPC and subnet, primary and secondary CIDR ranges, Private Google Access and its associated routes and DNS * **GKE control plane** — cluster version, private cluster settings (private nodes, endpoint access), master authorized networks and CIDR, workload identity pool, logging and system components, cluster autoscaling, KMS settings * **GKE node pools** — machine type, disk, image type, autoscaling settings, node locations and versions, spot/preemptible flag, OAuth scopes, labels, taints, network tags, GPU settings, upgrade and management settings * **Load balancer** — the external static IP Settings like NAT port allocation and node pool configuration can be changed safely from the Porter dashboard. For any requirement not exposed there (for example, public node IPs to reduce egress cost), contact Porter support before changing it in GCP. ## Migrating from a Service Account JSON If you previously connected GCP using a service account JSON key, you can switch to Workload Identity Federation with no downtime: 1. In Porter, navigate to **Integrations** → **Cloud accounts** and select your GCP account. 2. Click **Migrate to Workload Identity Federation** in the banner at the top of the page. 3. Follow the same Cloud Shell flow above. Your existing clusters keep authenticating with the JSON key while the new federation is being verified. Once verified, Porter swaps the credential atomically. There is no service interruption. ### After Migration Workload Identity Federation is now the active connection between Porter and your GCP project, but your previous service account and its JSON key are still present in your project until you remove them. To finish the migration: 1. In the [GCP Console](https://console.cloud.google.com), open **IAM & Admin** → **Service Accounts**. 2. Find your legacy service account (the one whose key you previously uploaded to Porter — typically named `porter-manager`, separate from the new `porter-manager-*` account created during federation). 3. Delete its JSON key, or delete the entire service account. Porter no longer needs the legacy account once federation is active. Leaving it in place means an unrotated long-lived key continues to exist in your project. ## Revoking Access To disconnect Porter from your GCP project: 1. First, delete any clusters through the Porter dashboard. 2. In the [GCP Console](https://console.cloud.google.com), navigate to **IAM & Admin** → **Workload Identity Federation** and delete the `porter-pool-*` Workload Identity Pool. This immediately invalidates all federated tokens. Optionally, also delete the `porter-manager-*` service account under **IAM & Admin** → **Service Accounts** for full cleanup. Porter connects to Azure using [workload identity federation](https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation). You create a [managed identity](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/overview) that trusts a Porter-managed OIDC issuer. Porter generates short-lived JWT tokens using this OIDC isser and exchanges them with Azure for short-lived Azure tokens. ## Prerequisites * You must have permissions to create app registrations, role definitions, and role assignments in your Azure subscription * You must have the [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli) and [`jq`](https://jqlang.github.io/jq/) installed ## Connect Your Azure Subscription In the Porter dashboard, click **Connect a new account** and select **Azure**. Porter generates a one-time setup command with your OIDC subject and issuer already substituted. Keep this open — you'll paste it into your terminal in the next step. Copy the commands shown in the dashboard and run it in your terminal. The commands look like: ```bash theme={null} # Authenticate the Azure CLI az login # Download the setup script curl -O https://raw.githubusercontent.com/porter-dev/docs/main/scripts/setup-azure-porter-wif.sh # Make it executable chmod +x setup-azure-porter-wif.sh # Run the script with the OIDC subject and issuer from Porter ./setup-azure-porter-wif.sh \ --subject \ --issuer ``` The script performs the initial setup in your Azure account: * Enables the required Azure resource providers: * Microsoft.Capacity * Microsoft.Compute * Microsoft.ContainerRegistry * Microsoft.ContainerService * Microsoft.ManagedIdentity * Microsoft.Network * Microsoft.OperationalInsights * Microsoft.OperationsManagement * Microsoft.ResourceGraph * Microsoft.Resources * Microsoft.Storage * Creates the custom `porter-aks-restricted` role and grants all subscription actions except: * `Microsoft.Authorization/elevateAccess/Action` * `Microsoft.Blueprint/blueprintAssignments/write` * `Microsoft.Blueprint/blueprintAssignments/delete` * `Microsoft.Compute/galleries/share/action` * Creates the `azure-porter-federated-sp` app registration and matching service principal, then assigns the custom role at the subscription scope * Adds these Microsoft Graph application permissions: * Application.ReadWrite.All * Directory.ReadWrite.All * Domain.Read.All * Group.Create * Group.ReadWrite.All * RoleManagement.ReadWrite.Directory * User.ReadWrite.All * Grants admin consent for the Graph permissions * Adds a federated identity credential on the app registration trusting Porter's OIDC issuer (audience `api://AzureADTokenExchange`) * Waits for Azure's [eventually consistent IAM service](https://devblogs.microsoft.com/identity/designing-for-eventual-consistency-for-microsoft-entra/) to propogate changes. * Outputs the app registration's metadata: **Subscription ID**, **Application (Client) ID**, and **Tenant ID** Setup can take a few minutes. If the script fails to grant admin consent automatically, grant it manually in the Azure Portal: **App registrations** > **azure-porter-federated-sp** > **API permissions** > **Grant admin consent for Default Directory**. Paste the outputted **Subscription ID**, **Application (Client) ID**, and **Tenant ID** back into the Porter dashboard to register the connection. ## Revoking Access To revoke Porter's access: 1. In the Azure portal, search for **App registrations** and delete **azure-porter-federated-sp** 2. \[Optional] Delete the custom role definition created by Porter This ensures that Porter can no longer access your Azure subscription. # Creating a cluster Source: https://docs.porter.run/cloud-accounts/creating-a-cluster Provision a managed Kubernetes cluster in your AWS, GCP, or Azure cloud account by selecting a region, machine type, and initial instance count After [connecting your cloud account](/cloud-accounts/connecting-a-cloud-account), you can create a cluster. Porter handles all the complexity of cluster provisioning, including networking, load balancers, and node groups. ## Provisioning Your Cluster Porter displays estimated monthly costs for your infrastructure (\~\$225/month for AWS). These estimates are for the default cluster configuration. Actual costs vary based on usage, region, and customizations. Review the cost breakdown and click **Accept** to continue. Porter pre-configures your cluster with sensible defaults: * **Cluster name**: Auto-generated based on your project * **Region**: Defaults to `us-east-1` * **Node groups**: EKS clusters are initialially provisioning with cost-optimal node groups. You can edit your node group configuration after provisioning is complete For guidance on choosing a region: if you have an external database, choose that region or a region as close as possible. Otherwise, choose a region near your primary customer base. You can customize these settings or accept the defaults. If AWS is limiting your account's resource quota, Porter displays a warning and offers to auto-request quota increases on your behalf. **Allow Porter to auto-request AWS quota** is enabled by default. This is the recommended approach. Alternatively, you can manually request quota increases through the [AWS Service Quotas console](https://console.aws.amazon.com/servicequotas/). If you go the manual route, you won't be able to provision until the quota increase requests are approved. Click **Provision** to start creating your infrastructure. Provisioning takes approximately 30-45 minutes. You can close the browser and return later — Porter continues working in the background. ### Troubleshooting If your cluster has been provisioning for more than 45 minutes, there may be an issue: * Verify that the IAM role still exists in your AWS account * Check your AWS Service Quotas to ensure they were approved * Verify that the selected region supports the requested instance types If issues persist, contact us through the dashboard chat bot with your project ID. If you encounter permission errors: * Verify the CloudFormation stack created successfully and the role exists * Ensure the role has not been modified after creation * Check that your AWS account has not applied SCPs (Service Control Policies) that restrict Porter's actions AWS typically approves quota increases automatically, but some may require manual review: * Check the status of quota requests in the [AWS Service Quotas console](https://console.aws.amazon.com/servicequotas/) * Requests under manual review typically take 24-48 hours * If urgent, contact AWS support to expedite the review If the CloudFormation stack fails to create: * Ensure you have sufficient permissions in your AWS account to create IAM roles * Check that you're logged into the correct AWS account (the account ID should match) * Verify your account is in good standing and billing is enabled Porter displays estimated monthly costs for your infrastructure (\~\$253/month for GCP). These estimates are for the default cluster configuration. Actual costs vary based on usage, region, and customizations. Review the cost breakdown and click **Accept** to continue. Before provisioning, Porter verifies that your cloud account credentials are connected and that billing is enabled on your GCP project. If any checks fail, troubleshooting steps are shown on the dashboard. Porter pre-configures your cluster with sensible defaults: * **Cluster name**: Auto-generated based on your project * **Region**: Defaults to `us-east1` * **Node groups**: Pre-configured with appropriate instance types For guidance on choosing a region: if you have an external database, choose a region close to it. Otherwise, choose a region near your primary customer base. You can customize these settings or accept the defaults. Click **Provision** to start creating your infrastructure. Provisioning takes approximately 30-45 minutes. You can close the browser and return later—Porter continues working in the background. ### Troubleshooting If your cluster has been provisioning for more than 45 minutes: * Verify the service account still exists and has the **Project IAM Admin** role * Verify the **Cloud Resource Manager API** is enabled * Verify the GCP project has billing enabled If issues persist, contact us through the dashboard chat bot with your project ID. Porter automatically enables required APIs during setup. If you still see API errors: 1. Verify the `porter-manager-*` service account still has its bootstrap roles (`roles/serviceusage.serviceUsageAdmin`, `roles/resourcemanager.projectIamAdmin`, `roles/iam.serviceAccountAdmin`). Porter needs these to enable additional APIs and grant itself the heavier roles. 2. Navigate to **APIs & Services** in the GCP Console and verify the five bootstrap APIs are enabled: **Cloud Resource Manager**, **IAM**, **IAM Credentials**, **STS**, and **Service Usage**. 3. Return to Porter and retry provisioning. If any of the bootstrap APIs were disabled after onboarding, re-enable them in the GCP Console. Porter cannot enable Service Usage programmatically if it has been turned off. Porter automatically provisions all required IAM bindings from the bootstrap roles. If you encounter permission errors: * Verify the `porter-manager-*` service account still has its bootstrap roles (`serviceusage.serviceUsageAdmin`, `resourcemanager.projectIamAdmin`, `iam.serviceAccountAdmin`). * Verify the `porter-pool-*` Workload Identity Pool exists and its provider is still bound to the service account. * Ensure neither the service account nor the pool has been deleted since onboarding. Porter cannot authenticate without them. * Ensure a billing account is attached to the project. Porter displays estimated monthly costs for your infrastructure (\~\$165/month for Azure). These estimates are for the default cluster configuration. Actual costs vary based on usage, region, and customizations. Review the cost breakdown and click **Accept** to continue. By default, Azure limits the types of resources you can provision. Before provisioning, you may need to request quota increases. In the Azure portal: 1. Navigate to your subscription 2. Select **Usage + quotas** 3. Set the resource filter to **Compute** and region to your desired region Request increases for: | Resource Family | Recommended Quota | | --------------------------- | ----------------- | | Total Regional vCPUs | 40 | | Standard Basv2 Family vCPUs | 40 | Click **Request quota increase** for each resource. Requests are typically approved automatically within a few minutes. If not, fill out the support ticket as prompted. Porter pre-configures your cluster with sensible defaults: * **Cluster name**: Auto-generated based on your project * **Region**: Defaults to `eastus` * **Azure tier**: Free tier for non-production, Standard tier for production * **Node groups**: Pre-configured with appropriate instance types For guidance on choosing a region: if you have an external database, choose a region close to it. Otherwise, choose a region near your primary customer base. You can customize these settings or accept the defaults. The Azure tier can be changed after cluster creation. Click **Provision** to start creating your infrastructure. Provisioning takes approximately 30-45 minutes. You can close the browser and return later—Porter continues working in the background. ### Troubleshooting If your cluster has been provisioning for more than 45 minutes: * Verify the service principal still exists and has the required permissions * Check that all resource providers are registered * Verify your compute quota requests were approved If issues persist, contact us through the dashboard chat bot with your project ID. If you see errors about insufficient quota: 1. Navigate to **Usage + quotas** in your Azure subscription 2. Check the status of your quota requests 3. If pending, wait for approval or contact Azure support 4. Once approved, retry provisioning in Porter If you see errors about resource providers: 1. Navigate to your subscription's **Resource providers** page 2. Find the mentioned provider and click **Register** 3. Wait for registration to complete 4. Retry provisioning in Porter If you encounter permission errors: * Verify the service principal has the `porter-aks-restricted` role * Check that admin consent was granted for Microsoft Graph permissions * Ensure the client secret hasn't expired *** ## After Provisioning Once your cluster is ready, you'll see the Porter dashboard. From here you can: Deploy an application from GitHub or a container registry. Customize instance types and scaling settings. Monitor cluster health and resource usage. # Deleting a cluster Source: https://docs.porter.run/cloud-accounts/deleting-a-cluster Remove a Porter-provisioned Kubernetes cluster and automatically clean up associated cloud resources like load balancers and node groups Deleting a cluster removes all applications and data running on it. This action cannot be undone. ## Before You Delete Before deleting your cluster: 1. **Back up any data** stored in your applications or persistent volumes 2. **Export environment variables** and secrets if you need them for future deployments 3. **Note your configuration** if you plan to recreate the cluster later *** ## Delete the Cluster Porter needs the IAM role to delete resources. Delete the cluster first, then delete the IAM role. If you've already deleted the IAM role, you'll need to [delete resources directly from the AWS console](/other/deleting-dangling-resources). 1. Navigate to the **Infrastructure** tab in the Porter dashboard 2. Click **Delete Cluster** 3. Confirm the deletion This process may take up to 30 minutes. After the cluster is deleted: 1. Navigate to **CloudFormation Stacks** in your AWS console 2. Select the stack named `PorterRole` 3. Click **Delete** This revokes Porter's access to your AWS account. Check your AWS console to verify all resources have been removed: * **EC2**: No instances, load balancers, or security groups related to the cluster * **EKS**: No clusters remaining * **VPC**: No VPCs created by Porter * **ECR**: Container images may remain (delete manually if not needed) Deleting resources via Porter may result in dangling resources. See [Deleting Dangling Resources](/other/deleting-dangling-resources) for cleanup guidance. 1. Navigate to the **Infrastructure** tab in the Porter dashboard 2. Click **Additional Settings** 3. Click **Delete Cluster** 4. Confirm the deletion This process may take up to 30 minutes. Check your GCP console to verify all resources have been removed: * **Compute Engine**: No instances or load balancers related to the cluster * **Kubernetes Engine**: No clusters remaining * **VPC networks**: No networks created by Porter * **Artifact Registry**: Container images may remain (delete manually if not needed) If you no longer need Porter to access your GCP project: 1. Navigate to **IAM & Admin** → **Service Accounts** 2. Find and delete the Porter service account Deleting resources via Porter may result in dangling resources. You can remove dangling resources via the GCP console or the gcloud CLI. 1. Navigate to the **Infrastructure** tab in the Porter dashboard 2. Click **Additional Settings** 3. Click **Delete Cluster** 4. Confirm the deletion This process may take up to 30 minutes. After deletion, verify in the Azure portal: 1. Search for **Resource groups** 2. A resource group named `-` may remain, containing your build images 3. Porter does not delete build images by default—delete this resource group manually if needed 4. Delete any other remaining resource groups created by Porter If you no longer need Porter to access your Azure subscription: 1. Search for **App registrations** in the Azure portal 2. Find and delete the Porter service principal 3. Optionally, delete the custom `porter-aks-restricted` role definition Deleting resources via Porter may result in dangling resources. Check your Azure portal to verify all resources have been removed. *** ## Troubleshooting Deletion If deletion takes more than 45 minutes: 1. Check your cloud provider's console for any resources in a "deleting" state 2. Look for dependencies that may be blocking deletion (e.g., load balancers with active connections) 3. Contact us through the dashboard chat bot with your project ID If resources remain in your cloud account after Porter reports deletion complete: 1. Follow the [Deleting Dangling Resources](/other/deleting-dangling-resources) guide 2. Check for resources in different regions than expected 3. Look for resources with names containing your project ID If Porter can't delete because it can no longer authenticate to your cloud: **AWS**: Re-create the CloudFormation stack to restore the IAM role, then delete the cluster. **GCP**: Workload Identity Federation tokens are minted on demand and do not expire. If Porter can't authenticate, either the `porter-pool-*` Workload Identity Pool or the `porter-manager-*` service account was deleted. Re-run the connect flow in **Integrations** → **GCP** to recreate them, then delete the cluster. **Azure**: Generate a new client secret and update it in **Integrations** → **Azure**, then delete the cluster. If you can't restore credentials, you'll need to [delete resources manually](/other/deleting-dangling-resources). # Node groups Source: https://docs.porter.run/cloud-accounts/node-groups Configure node groups with custom instance types, enable cost optimization with smart instance selection, and manage compute capacity Node groups are collections of compute instances that run your workloads. Porter provides flexible options for managing compute resources, including custom node groups for specialized workloads and cost optimization features. ## Default Node Groups Porter provisions three node groups by default: | Node Group | Purpose | Autoscaling | | --------------- | ----------------------------------- | ------------------------ | | **System** | Kubernetes system workloads | Fixed | | **Monitoring** | Observability stack (metrics, logs) | Fixed | | **Application** | Your application workloads | Enabled (1 node minimum) | The application node group autoscales based on demand. You can customize instance types, node counts, and scaling limits for all node groups. *** ## Changing Instance Types You can modify the instance type for any node group from the Infrastructure settings. From your Porter dashboard, click on the **Infrastructure** tab in the left sidebar. Click on **Overview** to view your cluster configuration and node groups. Expand the node group dropdown (e.g., **Default node group**) to see the following settings: | Setting | Description | | ----------------- | ------------------------------------------------------------------ | | **Machine type** | The underlying instance for nodes. Options vary by cloud provider. | | **Maximum nodes** | Upper limit for autoscaling under high load | | **Minimum nodes** | Lower limit for autoscaling under low load | | **Disk size** | Storage capacity for each node (default: 50GB) | Select the appropriate **Machine type** from the dropdown, then scroll to the bottom and click **Update**. ### Instance Type Pricing ### Why is my instance type not available? Instance types may not be available if: * The instance is not available in your cluster's region (cloud providers release new instances on a phased, region-by-region basis) * The instance type hasn't been validated by Porter for compatibility If you need a specific instance type that isn't listed, contact us through the dashboard chat bot. *** ## Creating a Custom Node Group Create custom node groups for specialized workloads like GPU processing, high-memory applications, or isolated environments. From your Porter dashboard, click on the **Infrastructure** tab in the left sidebar. Click on **Cluster** to view your cluster configuration and node groups. Click **Add an additional node group** to open the configuration panel. Choose between two configuration approaches: Cost optimization automatically selects the most cost-effective instance types for your workloads. Any cluster provisioned in AWS will default to cost optimized node groups. Set your **maximum CPU cores limit** to prevent unexpected scaling. This caps infrastructure costs while allowing flexibility in instance selection. ### Restricting per-instance size By default, Karpenter can satisfy a cost-optimized node group with instances of any size, including a single very large instance. Enable **Restrict per-instance CPU** on the node group form to bound the vCPUs of each instance Karpenter provisions. Use this when you want to spread workloads across more, smaller nodes to shrink the blast radius of a single Spot interruption or node failure. Setting the minimum equal to the maximum pins the node group to instances of that exact vCPU size while still allowing Karpenter to diversify across instance families. | Field | Description | | -------------------------- | ---------------------------------------------------------------------------- | | **Min vCPUs per instance** | Smallest instance vCPU count Karpenter may pick. Leave empty for no minimum. | | **Max vCPUs per instance** | Largest instance vCPU count Karpenter may pick. Leave empty for no maximum. | Min must be less than or equal to Max, and Min must not exceed the node group's **Max CPU** limit. ### Limitations The following configurations should use fixed instance types instead: * GPU instances (e.g., instances with NVIDIA GPUs) * Spot instances * Instances in public subnets * Instances with specialized hardware requirements Fixed node groups use a specific instance type. Applications scheduled on this group run only on the exact instance type you specify. This gives you precise control but may result in over-provisioning if configured incorrectly. Configure: | Setting | Description | | ----------------- | ---------------------------------------------------- | | **Instance type** | The machine type for nodes in this group | | **Minimum nodes** | Minimum number of nodes (set to 0 for scale-to-zero) | | **Maximum nodes** | Upper limit for autoscaling | For GPU workloads, select instances with GPU support: * **AWS**: `g4dn.xlarge`, `p3.2xlarge` * **Azure**: `Standard_NC4as_T4_v3` * **GCP**: `g2-standard-4` **Health Checks Required**: For production applications on cost-optimized node groups, configure proper [health checks](/applications/configure/zero-downtime-deployments#health-checks). This ensures applications can be safely rescheduled as nodes are reshuffled. Click **Save** to create the node group. Porter provisions the new nodes, which may take a few minutes. *** ## Public node groups By default, nodes in a Porter-managed cluster are private: they sit in a private subnet (AWS) or have private IPs only (GCP) and reach the internet through a NAT gateway. You can opt a custom node group into public networking when workloads need to receive inbound traffic directly or when you want to avoid NAT egress costs. Public nodes are reachable from the internet. Only enable this for workloads that need it, and rely on security groups, network policies, and firewall rules to limit exposure. To enable public networking on a node group: 1. From the node group form, locate the **Use public subnet** (AWS) or **Use public IP addresses** (GCP) toggle. 2. Turn the toggle on and click **Update** or **Save**. | Cloud | Toggle | Effect | | ----- | ----------------------- | ------------------------------------------------------- | | AWS | Use public subnet | Nodes launch in a public subnet and receive public IPs. | | GCP | Use public IP addresses | Nodes receive public IPs and skip Cloud NAT for egress. | On GCP, this setting is immutable for the lifetime of a node pool. Flipping it triggers a replacement pool; existing nodes are cordoned and drained before the old pool is removed, so running workloads stay available during the switch. *** ## Assigning Workloads to Node Groups Once your custom node group is created, assign applications to run on it: 1. Navigate to your application in the Porter dashboard 2. Go to the **Services** tab 3. Click the service you want to assign 4. Under **General**, find the **Node group** selector 5. Select your custom node group from the dropdown 6. Save and redeploy your application *** ## Deleting a Node Group Ensure no workloads are scheduled on the node group before deleting. Workloads will be disrupted if their node group is removed. To remove a custom node group: 1. Migrate any workloads running on the node group to another node group 2. Navigate to **Infrastructure** → **Cluster** 3. Find the node group you want to delete 4. Click the delete icon and confirm # Cloud accounts Source: https://docs.porter.run/cloud-accounts/overview Connect your AWS, GCP, or Azure account to Porter and provision managed Kubernetes clusters, node groups, and networking infrastructure Porter provisions and manages infrastructure directly in your own cloud account. This gives you full control over your data and resources while Porter handles the complexity of Kubernetes cluster management. ## Supported Cloud Providers Amazon Web Services Google Cloud Platform Microsoft Azure ## Getting Started Setting up Porter with your cloud account involves three steps: Provide Porter with credentials to access your cloud provider. Porter uses secure methods like IAM role assumption (AWS), service principals (Azure), or Workload Identity Federation (GCP) to manage resources without storing static credentials. [Connect a cloud account →](/cloud-accounts/connecting-a-cloud-account) Porter provisions a Kubernetes cluster with sensible defaults including networking, load balancers, and node groups. Provisioning takes approximately 30-45 minutes. [Create a cluster →](/cloud-accounts/creating-a-cluster) Once your cluster is ready, deploy applications from GitHub or a container registry. [Deploy your first app →](/getting-started/quickstart#step-4-create-your-first-application) *** ## What Porter Provisions Infrastructure provisioned by Porter includes: | Component | AWS | Azure | GCP | | ---------------------- | --------------------- | ------------------- | ------------------- | | **Virtual Network** | VPC | VNet | VPC | | **Load Balancer** | Network Load Balancer | Azure Load Balancer | Cloud Load Balancer | | **Kubernetes Cluster** | EKS | AKS | GKE | | **Container Registry** | ECR | ACR | Artifact Registry | ### Default Node Groups Porter provisions three node groups by default: | Node Group | Purpose | AWS | Azure | GCP | | --------------- | --------------------------- | ------------------------- | ----------------------------- | ------------------------- | | **System** | Kubernetes system workloads | 2× t3.medium | 2× Standard\_B2s | 2× e2-medium | | **Monitoring** | Observability stack | 1× t3.large | 1× Standard\_B2ms | 1× e2-standard-2 | | **Application** | Your workloads | 1× t3.medium (autoscales) | 1× Standard\_B2s (autoscales) | 1× e2-medium (autoscales) | On AWS, the application node group uses [cost optimization](/cloud-accounts/node-groups#creating-a-custom-node-group) by default, which automatically selects the most cost-effective instance types for your workloads. The application node group autoscales based on workload demand. All nodes include 50GB of disk storage by default. You can customize machine types, node counts, and disk sizes after initial provisioning through the [Node Groups](/cloud-accounts/node-groups) settings. *** ## FAQ ### How much does the underlying infrastructure cost? The cost varies based on resource usage. By default, clusters provisioned by Porter cost approximately: | Provider | Estimated Monthly Cost | | -------- | ---------------------- | | AWS | \~\$201/month | | GCP | \~\$253/month | | Azure | \~\$165/month | These estimates are for the default cluster configuration. Actual costs vary based on usage, region, and customizations. **All infrastructure costs can be covered with cloud credits from AWS, Google Cloud, or Azure.** For a full per-component breakdown, see [Infrastructure pricing](/cloud-accounts/pricing). ### Can I use my existing cloud credits? Yes. Porter provisions infrastructure directly in your cloud account, so any credits you have with AWS, GCP, or Azure apply to the resources Porter creates. ### What permissions does Porter need? Porter requires permissions to create and manage Kubernetes clusters, networking resources, and container registries. The setup process varies by provider: * **AWS**: Automatic setup — Porter guides you through creating a CloudFormation stack that provisions the required IAM role with one click * **Azure**: Manual setup required — you'll need to create a service principal using our setup script or the Azure CLI * **GCP**: One-line Cloud Shell setup via Workload Identity Federation. No service account keys to download or rotate. For detailed permission requirements and setup instructions, see [Connecting a Cloud Account](/cloud-accounts/connecting-a-cloud-account). ### Can I revoke Porter's access? Yes. You can revoke Porter's access at any time by deleting the IAM role (AWS), service principal (Azure), or Workload Identity Pool (GCP). Note that Porter will no longer be able to manage or delete resources after access is revoked. # Concepts Source: https://docs.porter.run/getting-started/concepts Understand Porter's core architecture including cloud accounts, clusters, applications, services, and how they fit together for deployments Check out our [deployment documentation](/applications/deploy/overview), [infrastructure provisioning guides](/cloud-accounts/overview), and more.

Cloud Account

Your cloud provider account where Porter provisions and manages infrastructure. Porter connects to your AWS, GCP, or Azure account using secure access methods, maintaining full ownership and control in your own cloud. All infrastructure costs can be covered with your cloud credits.
Cloud account connection

Cluster

A managed Kubernetes cluster that Porter provisions in your cloud account to run your applications. Porter abstracts away the complexity of Kubernetes while intelligently allocating your applications across available nodes based on resource requirements.
Cluster configuration

Node Group

Groups of compute instances that make up your cluster. Porter provisions three default node groups: System (for Kubernetes workloads), Monitoring (for observability), and Application (for your workloads, with autoscaling).
Node groups configuration

Application

A group of services that share the same build and environment variables. When you deploy from a Git repository, Porter builds your code once and can run multiple services from that same build. Porter also supports deployment from both public and private Docker registries.
Configure applications

Service

Individual processes that make up your application. Each service can have different start commands, resource allocations, and configurations. Porter supports three types: **Web** (HTTP traffic), **Worker** (background processes), and **Job** (scheduled or on-demand tasks).
Service types
# Introduction Source: https://docs.porter.run/getting-started/introduction Porter is a PaaS that runs in your own AWS, GCP, or Azure account. Deploy applications with a few clicks while keeping full cloud control. ## What is Porter?