Registry Configuration

Complete reference for the registry.yml configuration file.

File Location

Default location: ./registry.yml

Override with:

  • Environment variable: export REGISTRY_FILE=/path/to/registry.yml
  • CLI flag: portoser --registry /path/to/registry.yml
  • Config file: ~/.portoser/config.yml

File Format

The registry is a YAML file with two main sections:

machines:
  # Machine definitions

services:
  # Service definitions

Machines Section

Complete Example

machines:
  m1:
    host: "192.168.0.245"
    user: "admin"
    platform: "darwin"
    ssh_key: "~/.ssh/id_rsa"
    ssh_port: 22
    vault_role: "m1"
    vault_role_id: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    vault_secret_id: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    description: "Main macOS server"
    tags:
      - production
      - macos
      - high-memory

Required Fields

Field Type Description
host string IP address or hostname
user string SSH username
platform string Operating system: darwin, linux, or bsd

Optional Fields

Field Type Default Description
ssh_key string ~/.ssh/id_rsa Path to SSH private key
ssh_port integer 22 SSH port number
vault_role string - Vault AppRole name
vault_role_id string - Vault AppRole ID
vault_secret_id string - Vault AppRole secret ID
description string - Human-readable description
tags array [] Tags for organization

Services Section

Docker Service Example

services:
  my-docker-app:
    type: "docker"
    path: "/opt/my-docker-app"
    machine: "m1"
    compose_file: "docker-compose.yml"
    env_file: ".env"
    port: 8080
    ports:
      - 8080
      - 8443
    health:
      type: "http"
      endpoint: "/health"
      port: 8080
      interval: 30
      timeout: 5
      retries: 3
      expected_status: 200
    depends_on:
      - postgres
      - redis
    env:
      NODE_ENV: "production"
      LOG_LEVEL: "info"
    vault_secrets:
      - path: "secret/data/services/my-app"
        keys:
          - api_key
          - jwt_secret
    resources:
      memory: "512M"
      cpus: "0.5"
    tags:
      - production
      - api
    description: "Main API service"

Local (Python/UV) Service Example

services:
  my-python-app:
    type: "local"
    path: "/opt/my-python-app"
    machine: "m1"
    command: "uv run uvicorn main:app --host 0.0.0.0 --port 8000"
    port: 8000
    log_file: "/var/log/my-python-app.log"
    pid_file: "/var/run/my-python-app.pid"
    health:
      type: "http"
      endpoint: "/health"
      interval: 30
    env:
      PYTHONUNBUFFERED: "1"
      APP_ENV: "production"
    depends_on:
      - postgres

Native Service Example

services:
  my-native-service:
    type: "native"
    path: "/opt/my-service"
    machine: "m1"
    service_name: "my-service"
    command: "./start.sh"
    port: 9000
    health:
      type: "tcp"
      port: 9000

Required Fields

Field Type Description
type string Service type: docker, local, or native
path string Absolute path to service directory
machine string Target machine name (must exist in machines section)

Optional Fields

Field Type Default Description
port integer - Primary service port
ports array [] Additional ports
command string - Start command (for local/native types)
compose_file string docker-compose.yml Docker Compose file name
env_file string - Environment file path
service_name string service key System service name
log_file string - Log file path
pid_file string - PID file path
description string - Human-readable description
tags array [] Tags for organization

Health Checks

HTTP Health Check

health:
  type: "http"
  endpoint: "/health"
  port: 8080
  interval: 30        # seconds between checks
  timeout: 5          # request timeout
  retries: 3          # failed attempts before marking unhealthy
  expected_status: 200
  headers:
    Authorization: "Bearer token"

TCP Health Check

health:
  type: "tcp"
  port: 5432
  interval: 15
  timeout: 3
  retries: 2

Process Health Check

health:
  type: "process"
  pid_file: "/var/run/myapp.pid"
  interval: 60

Custom Health Check

health:
  type: "custom"
  command: "/opt/myapp/health-check.sh"
  interval: 30
  timeout: 10

Health Check Fields

Field Type Default Description
type string http Check type: http, tcp, process, custom
endpoint string / HTTP endpoint path
port integer service port Port to check
interval integer 30 Seconds between checks
timeout integer 5 Request/connection timeout
retries integer 3 Failed attempts threshold
expected_status integer 200 Expected HTTP status
command string - Custom check command
headers object {} HTTP headers

Dependencies

Define service dependencies:

services:
  web-app:
    # ... other config ...
    depends_on:
      - api
      - database
      - redis

  api:
    depends_on:
      - database

  database:
    # No dependencies

  redis:
    # No dependencies

Portoser will:

  • Start dependencies before dependent services
  • Check dependency health before deploying
  • Prevent starting if dependencies are unhealthy
  • Show dependency graph in Web UI

Environment Variables

Inline Variables

services:
  my-app:
    env:
      NODE_ENV: "production"
      PORT: "3000"
      API_URL: "https://api.example.com"
      DEBUG: "false"

Environment File

services:
  my-app:
    env_file: ".env.production"

Mixed Approach

services:
  my-app:
    env_file: ".env"
    env:
      # These override values from .env
      NODE_ENV: "production"
      DEBUG: "false"

Vault Secrets

Basic Configuration

services:
  my-app:
    vault_secrets:
      - path: "secret/data/services/my-app"
        keys:
          - api_key
          - database_url
          - jwt_secret

Results in environment variables:

  • API_KEY=<value from vault>
  • DATABASE_URL=<value from vault>
  • JWT_SECRET=<value from vault>

Custom Environment Variable Names

services:
  my-app:
    vault_secrets:
      - path: "secret/data/services/my-app"
        mapping:
          api_key: "MY_API_KEY"
          database_url: "DB_URL"

Results in:

  • MY_API_KEY=<value>
  • DB_URL=<value>

Multiple Secret Paths

services:
  my-app:
    vault_secrets:
      - path: "secret/data/services/my-app"
        keys: ["api_key"]
      - path: "secret/data/shared/database"
        keys: ["url", "username", "password"]
      - path: "secret/data/shared/smtp"
        keys: ["host", "port"]

Auto-Rotation

services:
  my-app:
    vault_secrets:
      - path: "secret/data/services/my-app"
        keys: ["api_key"]
        auto_rotate: true
        rotation_interval: "24h"

Resource Limits (Docker Services)

services:
  my-docker-app:
    resources:
      memory: "512M"      # Maximum memory
      memory_swap: "1G"   # Memory + swap limit
      cpus: "0.5"         # CPU cores (0.5 = 50% of one core)
      cpu_shares: 512     # Relative CPU weight
      pids_limit: 100     # Maximum PIDs

Common memory values:

  • 128M, 256M, 512M, 1G, 2G, 4G

Common CPU values:

  • 0.25 (25% of one core)
  • 0.5 (50% of one core)
  • 1 (one full core)
  • 2 (two cores)

Port Configuration

Single Port

services:
  my-app:
    port: 8080

Multiple Ports

services:
  my-app:
    ports:
      - 8080
      - 8443
      - 9090

Port Mapping (Docker)

services:
  my-app:
    port_mapping:
      - "80:8080"      # host:container
      - "443:8443"

Auto Port Assignment

services:
  my-app:
    port: "auto"  # Portoser assigns available port

Tags

Organize services and machines with tags:

machines:
  m1:
    tags:
      - production
      - macos
      - high-memory

services:
  api:
    tags:
      - production
      - api
      - customer-facing

  worker:
    tags:
      - production
      - background
      - async

Use tags to:

  • Filter lists: portoser service list --tag production
  • Bulk operations: portoser deploy --tag production
  • Organize in Web UI

Complete Example

# Complete registry.yml example

machines:
  m1:
    host: "192.168.0.245"
    user: "admin"
    platform: "darwin"
    description: "Main macOS server"
    tags: ["production", "macos"]

  mini1:
    host: "192.168.0.96"
    user: "ubuntu"
    platform: "linux"
    vault_role: "mini1"
    description: "Linux mini server"
    tags: ["production", "linux"]

services:
  postgres:
    type: "docker"
    path: "/opt/postgres"
    machine: "m1"
    port: 5432
    health:
      type: "tcp"
      port: 5432
      interval: 15
    vault_secrets:
      - path: "secret/data/services/postgres"
        keys: ["POSTGRES_USER", "POSTGRES_PASSWORD"]
    resources:
      memory: "256M"
      cpus: "0.5"
    tags: ["database", "production"]

  api:
    type: "local"
    path: "/opt/api"
    machine: "m1"
    command: "uv run uvicorn main:app --host 0.0.0.0 --port 8000"
    port: 8000
    health:
      type: "http"
      endpoint: "/health"
      interval: 30
    depends_on:
      - postgres
    env:
      APP_ENV: "production"
      LOG_LEVEL: "info"
    vault_secrets:
      - path: "secret/data/services/api"
        keys: ["API_KEY", "JWT_SECRET"]
      - path: "secret/data/shared/database"
        mapping:
          url: "DATABASE_URL"
    tags: ["api", "production"]

  web:
    type: "docker"
    path: "/opt/web"
    machine: "mini1"
    port: 80
    health:
      type: "http"
      endpoint: "/health"
    depends_on:
      - api
    env:
      API_URL: "http://192.168.0.245:8000"
    tags: ["web", "production"]

Validation

Validate your registry:

# Check syntax and structure
portoser registry validate

# Check for common issues
portoser registry check

# Dry-run deployment
portoser deploy my-app --dry-run

Best Practices

1. Use Version Control

git add registry.yml
git commit -m "Update service configuration"

2. Document Changes

services:
  my-app:
    # Changed port from 8000 to 8080 on 2024-01-15
    # Reason: Conflict with another service
    port: 8080

3. Use Descriptive Names

# Good
services:
  api-gateway-production:
  user-service-staging:
  postgres-primary:

# Avoid
services:
  app1:
  svc2:
  db:

4. Always Use Health Checks

services:
  critical-service:
    health:
      type: "http"
      endpoint: "/health"
      interval: 15

5. Group Related Services

services:
  # Web Tier
  nginx:
    tags: ["web-tier"]

  # API Tier
  api-1:
    tags: ["api-tier"]
  api-2:
    tags: ["api-tier"]

  # Data Tier
  postgres:
    tags: ["data-tier"]
  redis:
    tags: ["data-tier"]

6. Use Vault for Secrets

# Bad
services:
  my-app:
    env:
      API_KEY: "secret-key-123"  # DON'T DO THIS

# Good
services:
  my-app:
    vault_secrets:
      - path: "secret/data/myapp"
        keys: ["API_KEY"]

Troubleshooting

Invalid YAML Syntax

# Use a YAML validator
yamllint registry.yml

# Or portoser built-in validation
portoser registry validate

Service Not Found

# List all services
portoser service list

# Check registry path
echo $REGISTRY_FILE

# Reload registry
portoser registry reload

Machine Connection Failed

# Test SSH
ssh user@host

# Check machine config
portoser machine show m1

# Update configuration
portoser machine update m1 --host new-ip