⚠️ Porta is in beta — APIs and features may change before v1.0
Skip to content

Declarative Provisioning

The porta provision command creates organizations, applications, clients, roles, permissions, custom claims, role-permission mappings, users, application modules, and branding configurations from a single YAML or JSON file. It is the fastest way to set up a complete Porta environment from scratch or to replicate a known configuration across environments.

Quick Start

bash
# Preview what will be created (no changes)
porta provision -f my-setup.yaml --dry-run

# Apply the configuration
porta provision -f my-setup.yaml

# Apply with merge mode (skip existing, add new)
porta provision --file my-setup.yaml --mode merge

# JSON output for scripting
porta provision --file my-setup.yaml --json

Docker Usage

When running Porta via Docker, use the CLI wrapper script or stdin piping to pass provisioning files from the host to the container.

The porta wrapper script automatically detects host files and pipes them to the container:

bash
# Install the wrapper (one-time)
curl -fsSL https://raw.githubusercontent.com/blendsdk/porta-identity/main/docker/porta.sh \
  -o porta && chmod +x porta

# Use normally — host files work transparently
./porta provision -f setup.yaml --dry-run
./porta provision -f setup.yaml
./porta provision -f setup.yaml --mode overwrite

Without the Wrapper

Pipe the file via stdin using shell redirection:

bash
docker exec porta-app porta provision -f /dev/stdin < setup.yaml
docker exec porta-app porta provision -f /dev/stdin --dry-run < setup.yaml

Note: The file path passed to -f is resolved inside the container. Host paths like ./setup.yaml won't work with docker exec directly — use the wrapper or stdin piping instead.

File Format

Provisioning files use a nested, human-readable structure. The file describes organizations at the top level, with applications, clients, roles, permissions, and claims nested inside.

Minimum Required Fields

yaml
version: "1.0"

organizations:
  - name: My Organization

That's it. The slug will be auto-generated from the name (my-organization), and all optional fields use sensible defaults.

Full Structure

yaml
version: "1.0"                      # Required — file format version

config:                              # Optional — system configuration overrides
  access_token_ttl: "3600"
  refresh_token_ttl: "86400"
  session_ttl: "43200"

organizations:
  - name: Acme Corp                  # Required
    slug: acme                       # Optional — auto-generated from name
    default_locale: en               # Optional — defaults to "en"
    default_login_methods:           # Optional — defaults to [password, magic_link]
      - password
      - magic_link

    applications:
      - name: Customer Portal        # Required
        slug: customer-portal        # Optional — auto-generated from name
        description: Main app        # Optional

        clients:
          - client_name: Web App
            client_type: confidential
            application_type: web
            grant_types:
              - authorization_code
              - refresh_token
            redirect_uris:
              - https://portal.acme.com/callback
            response_types:
              - code
            scope: openid profile email

        permissions:
          - name: Read Customers
            slug: read-customers
          - name: Write Customers
            slug: write-customers
          - name: Delete Customers
            slug: delete-customers

        roles:
          - name: Admin
            slug: admin
            description: Full access to all resources
            permissions:             # Inline permission assignment
              - read-customers
              - write-customers
              - delete-customers
          - name: Viewer
            slug: viewer
            description: Read-only access
            permissions:
              - read-customers

        claim_definitions:
          - name: Department
            slug: department
            claim_type: string
            description: User department
          - name: Employee ID
            slug: employee-id
            claim_type: number

Field Reference

Top Level

FieldTypeRequiredDescription
versionstringYesFile format version (must be "1.0")
configobjectNoSystem configuration key-value overrides
allow_passwordsbooleanNoEnable password provisioning (default: false). See Password Provisioning.
organizationsarrayYesOne or more organizations to provision

Organization

FieldTypeRequiredDescription
namestringYesDisplay name
slugstringNoURL-friendly identifier (auto-generated from name if omitted)
default_localestringNoDefault locale code (e.g., en, fr)
default_login_methodsstring[]NoDefault login methods: password, magic_link
two_factor_policystringNo2FA policy: optional, required_email, required_totp, required_any
brandingobjectNoOrganization branding configuration. See Branding.
applicationsarrayNoNested applications for this organization
usersarrayNoUsers to create in this organization. See User.

Application

FieldTypeRequiredDescription
namestringYesDisplay name
slugstringNoURL-friendly identifier
descriptionstringNoApplication description
clientsarrayNoOIDC clients for this application
rolesarrayNoRBAC roles
permissionsarrayNoRBAC permissions
claim_definitionsarrayNoCustom claim definitions
modulesarrayNoApplication modules. See Module.

Client

FieldTypeRequiredDescription
client_namestringYesHuman-readable name
client_typestringYesconfidential or public
application_typestringNoweb or native
grant_typesstring[]NoOAuth grant types
redirect_urisstring[]NoAllowed redirect URIs
response_typesstring[]NoOAuth response types
scopestringNoSpace-separated scope string
login_methodsstring[]NoPer-client login method override (null = inherit from org)
post_logout_redirect_urisstring[]NoAllowed post-logout redirect URIs
allowed_originsstring[]NoCORS allowed origins
require_pkcebooleanNoRequire PKCE (default: true). Setting to false emits a security warning.
token_endpoint_auth_methodstringNoAuth method: client_secret_basic, client_secret_post, none
secretobjectNoSecret configuration for confidential clients. See Secret Config.

Role

FieldTypeRequiredDescription
namestringYesDisplay name
slugstringNoURL-friendly identifier
descriptionstringNoRole description
permissionsstring[]NoPermission slugs to assign (creates role-permission mappings)

Permission

FieldTypeRequiredDescription
namestringYesDisplay name
slugstringYesURL-friendly identifier
descriptionstringNoPermission description

Claim Definition

FieldTypeRequiredDescription
namestringYesDisplay name
slugstringYesURL-friendly identifier
claim_typestringYesValue type: string, number, boolean, json
descriptionstringNoClaim description

Config

The config section accepts key-value pairs that map to Porta's system configuration. Values are strings, numbers, or booleans:

yaml
config:
  access_token_ttl: "3600"       # Token TTL in seconds
  refresh_token_ttl: "86400"
  session_ttl: "43200"

Only existing configuration keys are updated. Unknown keys are ignored for safety.

User

Users can be nested under organizations. Roles and claim values reference applications by slug.

FieldTypeRequiredDescription
emailstringYesUser email address
given_namestringNoFirst name
family_namestringNoLast name
localestringNoLocale code (e.g., en)
statusstringNoactive or inactive (default: active)
email_verifiedbooleanNoWhether email is verified (default: false)
passwordstringNoInitial password (requires allow_passwords: true). See Password Provisioning.
rolesarrayNoRole assignments: [{ app: "app-slug", role: "role-slug" }]
claimsarrayNoClaim values: [{ app: "app-slug", claim: "claim-slug", value: ... }]

Module

Application modules represent sub-components of an application.

FieldTypeRequiredDescription
namestringYesDisplay name
slugstringYesURL-friendly identifier
descriptionstringNoModule description
statusstringNoactive or inactive (default: active)

Branding

Organization branding customization.

FieldTypeRequiredDescription
primary_colorstringNoCSS color value (e.g., #1a73e8)
company_namestringNoDisplay name for login pages
custom_cssstringNoCustom CSS for login templates
logo_urlstringNoURL to organization logo
favicon_urlstringNoURL to favicon

Secret Config

Optional secret configuration for confidential clients. Only allowed on confidential client types.

FieldTypeRequiredDescription
labelstringNoHuman-readable label for the secret
expires_atstringNoISO 8601 expiry date (e.g., 2027-01-01T00:00:00Z)
expires_instringNoDuration until expiry: 90d, 6m, 1y, 24h

Note: expires_at and expires_in are mutually exclusive — set one or neither.

Import Modes

ModeBehaviorUse Case
merge (default)Skip existing entities, create new onesSafe for re-running, incremental setup
overwriteReplace existing entities, create new onesReset to a known state
bash
# Merge mode (default) — safe to re-run
porta provision --file setup.yaml --mode merge

# Overwrite mode — replaces existing
porta provision --file setup.yaml --mode overwrite

Role-Permission Mappings

When roles include a permissions array, the provision command automatically creates role-permission mappings after the main import:

yaml
roles:
  - name: Admin
    slug: admin
    permissions:           # These permission slugs must match
      - read               # permission slugs defined in the same
      - write              # application (or already existing)

This is equivalent to manually running:

bash
porta app role assign-perm --app-id <app> --role admin --permission read
porta app role assign-perm --app-id <app> --role admin --permission write

Password Provisioning

By default, provisioning files cannot include passwords. This is a deliberate security decision — production environments should use invitation flows or magic links instead.

For development and testing only, you can enable password provisioning:

yaml
version: "1.0"
allow_passwords: true              # ⚠ Development/testing only!

organizations:
  - name: Dev Org
    slug: dev
    users:
      - email: admin@dev.example.com
        given_name: Admin
        password: "SecureP@ss123!"   # NIST SP 800-63B validated
        email_verified: true
        roles:
          - app: my-app
            role: admin

Security notes:

  • Passwords are validated against NIST SP 800-63B requirements (minimum 8 characters, no common patterns)
  • Passwords are hashed client-side with Argon2id before HTTP transport — plaintext never touches the wire or server
  • The allow_passwords flag is intentionally separate from the user data to make the security trade-off explicit
  • If a password field is present without allow_passwords: true, the command fails with a clear error

How It Works

  1. Parse — Reads the YAML/JSON file and validates against the schema
  2. Transform — Converts the nested structure to a flat import manifest (includes client-side password hashing if applicable)
  3. Import — Sends the manifest to the Admin API import endpoint, which processes entities in 12 dependency-ordered phases within a single transaction
  4. Report — Displays a summary of created/skipped/updated/failed entities and client credentials

Import engine processing order (single transaction):

PhaseEntity TypeDependencies
1Organizations (+branding, 2FA policy)None
2ApplicationsOrganizations
3Clients + secretsOrganizations, Applications
4RolesApplications
5PermissionsApplications
6Claim definitionsApplications
7Role-permission mappingsRoles, Permissions
8Application modulesApplications
9UsersOrganizations
10User-role assignmentsUsers, Roles
11User claim valuesUsers, Claim definitions
12System config overridesNone

All phases execute within a single PostgreSQL transaction. Any error triggers a full ROLLBACK — no partial state.

Dry Run

Use --dry-run to preview what would be created without making any changes:

bash
porta provision --file setup.yaml --dry-run

Output shows the parsed manifest summary and validation results.

JSON Output

For scripting and CI/CD integration:

bash
porta provision --file setup.yaml --json

Returns structured JSON with import results.

File Formats

The command supports both YAML and JSON:

ExtensionFormat
.yamlYAML
.ymlYAML
.jsonJSON

Tips

  • Start simple — Begin with just organizations and applications, then add roles/permissions later
  • Use slugs — Explicitly set slugs for predictable identifiers (important for CI/CD)
  • Merge mode is safe — You can re-run the same file and it will skip existing entities
  • Validate first — Always use --dry-run before applying to a production environment
  • Version control — Keep your provisioning files in git alongside your application code
  • Environment variants — Create separate files for dev, staging, and production

Examples

The repository includes four ready-to-use example files at increasing complexity levels. Each can be applied directly or used as a starting point for your own provisioning files.

bash
# Preview any example without making changes
porta provision -f examples/provision-simple.yaml --dry-run

# Apply an example
porta provision -f examples/provision-simple.yaml
ExampleWhat It Demonstrates
provision-simple.yamlGetting started — Single org (acme), one app, public SPA client + confidential API client, 2 permissions, 2 roles with permission mappings, per-client login method override
provision-multi-org.yamlMulti-tenancy — Two isolated organizations (acme, globex), each with their own app, clients, permissions, and roles. Shows how tenant isolation works in provisioning
provision-enterprise.yamlEnterprise setup — One org with 2 applications (ERP + Customer Portal), 10 permissions, 7 roles, 3 clients (web + native + portal), 2 custom claim definitions, and system config TTL overrides
provision-full.yamlComplete feature showcase — Every provisioning feature in one file: org branding (color, company name, custom CSS), 2FA policy, login methods, 3 application modules, 3 roles with permission mappings, 7 permissions, 3 custom claims, public + confidential client with secret config (label + expiry), 3 users with passwords and role/claim assignments, system config. Requires allow_passwords: true

Progression path

Start with provision-simple.yaml to understand the basics, then graduate to provision-full.yaml when you need the complete feature set. Copy any example and customize it for your environment.

Released under the MIT License.