Portacode CI/CD Intro
Portacode runs CI/CD-style workflows from a simple YAML file. A workflow can do two powerful things:
If you want to run these workflows on your own infrastructure, first set up a Proxmox node: Set Up Proxmox As A Portacode Infra Node.
1 Deploy In A Clean Disposable Environment
Deploy something (or run tests/builds) in a clean, disposable environment. Portacode can provision a fresh managed container/server, run your steps (setup -> build -> test -> deploy), expose ports if needed, then notify you and optionally delete the environment on success.
2 Run Against An Existing Server
Run the same kind of steps on a server you already have. Useful when you want to deploy, test, or run a runbook against an existing staging/production device - still driven by the same YAML steps and the same success/failure behavior.
What Is portafile.yaml?
portafile.yaml is a YAML config that Portacode can load from:
/dashboard/?portafile=<URL-TO-YAML>
It can prefill:
- template selection
- device name / hostname
- resource sizing
- project paths
- automation behavior (
instructions,expose_ports,on_success,on_failure)
Two Ways To Run The YAML
1) One-Click Template (new device)
Use YAML with source_template to provision a new container/device, then optionally run automation instructions.
Browse ready-made templates here: One-Click Deployment Templates.
2) Existing Device AutomationTask
Use YAML without source_template to run steps on an already existing device.
This is useful for CI/CD jobs like test/build/deploy pipelines.
One-Click Template Params
| Param | Type / Allowed input | Default | Short description + example values |
|---|---|---|---|
source_template |
string template selector |
first available template on target node | Picks the base container template for one-click provisioning. See source_template matching rules for exact selector formats and examples. |
name |
string |
"Alpine" |
Device name for created server. Example: "test-runner-01" |
hostname |
string |
"alpine" |
Hostname for created server. Example: "ci-runner" |
username |
string |
"root" |
Username for the created server. Example: "deploy" |
project_paths |
array<string>, max 10 |
[] |
Project folders to create. Supports ~ and env vars like $HOME. Example: ["~/app", "$HOME/tests"] |
inputs |
array<object> |
[] |
Optional input definitions rendered as form fields before deploy / run. Example: [{ id: "telegram_bot_token", type: "secret", label: "Telegram bot token" }] |
inputs[].id |
string matching ^[A-Za-z][A-Za-z0-9_]*$ |
N/A (required per input) | Local input id used inside automation placeholders like ${inputs.telegram_bot_token}. Example: "telegram_bot_token" |
inputs[].type |
text | textarea | secret | number | boolean | email | url | select | multiselect | color | file |
"text" |
Controls which form field Portacode renders automatically. Example: "secret" |
inputs[].save |
string key or false |
local input id | Controls whether the field can be saved to the user's account for reuse. Use a string key like "openclaw.telegram.bot_token" for a reusable preset key, or false to hide the save toggle. |
inputs[].persist_environment |
boolean |
false |
When true, persists the submitted input as a system environment variable before any instruction runs. The variable name is the uppercased input id, for example openai_api_key becomes OPENAI_API_KEY. Use only for values that must be available to later sessions and services on the device. |
resources.disk_gib |
positive integer | 3 |
Requested disk size in GiB. Example: 8 |
resources.ram_mib |
positive integer | 1024 |
Requested RAM in MiB. Example: 2048 |
resources.cpus |
decimal > 0 |
0.1 |
Requested CPU share. Example: 1, 2 |
AutomationTask Params
Note: these params are also supported in one-click templates.
| Param | Type / Allowed input | Default | Short description + example values |
|---|---|---|---|
instructions |
array of steps. Each step can be a string command or object with run | cmd | command | wait_for |
N/A (required) | Ordered automation steps. wait_for polls an HTTP(S) URL until it returns 2xx; supports placeholders like https://[exposed:3000]/health (polls every 3s, request timeout 5s, default step timeout 600s, overridable via step timeout). |
inputs |
array<object> |
[] |
Optional runtime input definitions rendered live from YAML in the automation form. Values are stored separately from instructions and sent alongside the task. |
inputs[].id |
string matching ^[A-Za-z][A-Za-z0-9_]*$ |
N/A (required per input) | Local input id referenced from steps and metadata using ${inputs.<id>}. Example: ${inputs.site_name} |
inputs[].type |
text | textarea | secret | number | boolean | email | url | select | multiselect | color | file |
"text" |
Determines the rendered control and normalization rules for submitted values. |
inputs[].save |
string key or false |
local input id | Enables the optional "save to my account" toggle. A string sets the reusable saved-input key; false disables saving for that field. |
inputs[].persist_environment |
boolean |
false |
When true, persists the submitted input as a system environment variable before any instruction runs. The variable name is the uppercased input id, for example openai_api_key becomes OPENAI_API_KEY. Use only for values that must be available to later sessions and services on the device. |
requires_codex_connection |
boolean |
false |
Prevents a deployment or automation from starting unless the account owner has connected Codex in Account Overview. This is a Portacode server-side eligibility check; it is not sent to the device. Example: true. |
task_name |
string |
"Automation Task" |
Human-readable task name used in notifications. Example: "Nightly Test Run" |
expose_ports |
array<int | object> where each entry is either a port number or { port: <int>, protocol: http|https }, max 3 |
[] |
Ports to expose before steps run. Bare integers default to HTTP origins. Example: [3000, { port: 8443, protocol: https }] |
on_success |
enum: notify | notify_and_delete | do_nothing |
notify |
What to do after success. Example: notify_and_delete |
on_failure |
enum: notify | suggest_fixes | auto_fix | retry OR object { retry: <int> } |
notify |
What to do after failure. Examples: suggest_fixes, retry, { retry: 3 } |
For HTTPS/custom-domain exposure via expose_ports, also configure Cloudflare tunnel on that device:
Connect A Device To A Domain (Cloudflare Tunnel).
Persist An Input As An Environment Variable
Input values are available to each automation step while it runs. Set persist_environment: true only when a value must also be available to future sessions or services on the device. The value is persisted before the first instruction, under the uppercased input id.
inputs:
- id: openai_api_key
type: secret
required: true
persist_environment: true
instructions:
- run: my-client-command
This example makes OPENAI_API_KEY available to the automation and to later device sessions. Treat persisted secrets as system-wide device credentials; do not enable this for values that should be limited to a single automation run.
Codex Device Authentication
The Codex one-click template does not persist a ChatGPT or gateway token. After the account owner connects Codex under Account Overview, Codex CLI sends requests to the Portacode agent at http://127.0.0.1:61789/v1. The agent signs each request with the device identity and the gateway maps that identity to the owning Portacode account.
Templates or automations that require this account connection should set requires_codex_connection: true at the top level. Portacode checks it before provisioning or running the automation and rejects the request with guidance to connect Codex if the account is not connected. It does not create an environment variable, inject a credential, or alter the payload delivered to the device.
This listener is local-only and uses a fixed Portacode port. Do not expose it through firewall, proxy, or port-forwarding rules. The gateway records request and token usage against the authenticated device; the Codex usage report shows the resulting per-device totals.
To configure a paired device manually, run portacode prepare codex. It installs Node.js and Codex CLI when needed, writes the local-proxy Codex configuration, and verifies that the Portacode service is running the loopback proxy. New terminal sessions receive the required non-secret local marker automatically. To use Codex immediately in the terminal that ran the command, run export OPENAI_API_KEY=portacode-local first; a child process cannot modify its parent shell environment.
source_template Matching Rules
source_template is matched against the template catalog discovered from the target Proxmox node. This is intentionally dynamic, so values differ between infrastructures.
Supported selector forms:
- Exact template ID:
local:vztmpl/ubuntu-24.04-standard_24.04-2_amd64.tar.zst - Short name / stem / family prefix:
ubuntu,ubuntu-24.04,alpine,debian-12 - Version constraints:
- Prefix operator form:
<=ubuntu-24.04,>debian-11,=alpine-3.19 - Infix form:
ubuntu<=24.04,debian>11
- Prefix operator form:
- Comma-separated fallbacks (first match wins):
ubuntu-24.04, ubuntu-22.04, alpine
If no selector is provided, the UI uses the default selected template (derived from available templates for that node).
Examples
One-click template + automation
source_template: ubuntu<=24.04, ubuntu-22.04, alpine
name: test-runner
hostname: test-runner
username: deploy
resources:
disk_gib: 8
ram_mib: 2048
cpus: 1
project_paths:
- ~/app
automation_task:
instructions:
- run: npm run build
expose_ports:
- 3000
- port: 8443
protocol: https
Existing device AutomationTask
task_name: Smoke Test + Public Readiness
instructions:
- run: pytest -q
- wait_for: https://[exposed:8080]/
expose_ports:
- 8080
- port: 8443
protocol: https
on_success: notify_and_delete
on_failure:
retry: 3
Add A Deploy Button To Your GitHub README
[Deploy with Portacode](https://portacode.com/dashboard/?portafile=https%3A%2F%2Fraw.githubusercontent.com%2FOWNER%2FREPO%2Fmain%2Fportafile.yaml)
Replace OWNER, REPO, and branch as needed.