Service Registry

The Service Registry is the central configuration file that defines all machines, services, and their relationships in your Portoser cluster.

Overview

The registry is stored as a YAML file (registry.yml) and serves as the single source of truth for:

  • Machines: Physical or virtual machines where services run
  • Services: Applications and their configurations
  • Dependencies: Relationships between services
  • Health Checks: How to verify service health
  • Secrets: References to Vault secrets

Registry Structure

machines:
  machine-name:
    host: "IP or hostname"
    user: "SSH username"
    platform: "darwin|linux|bsd"
    vault_role: "vault-approle-id"
    ssh_key: "/path/to/key"

services:
  service-name:
    type: "docker|local|native"
    path: "/absolute/path/to/service"
    machine: "machine-name"
    port: 8080
    health:
      endpoint: "/health"
      type: "http|tcp"
      interval: 30
    depends_on:
      - other-service
    env:
      KEY: "value"
    vault_secrets:
      - path: "secret/data/myapp"
        keys: ["api_key", "database_url"]

Machines Section

Required Fields

  • host: IP address or hostname of the machine
  • user: SSH username for connecting to the machine
  • platform: Operating system type (darwin, linux, or bsd)

Optional Fields

  • ssh_key: Path to SSH private key (defaults to ~/.ssh/id_rsa)
  • vault_role: Vault AppRole ID for this machine
  • port: SSH port (defaults to 22)
  • description: Human-readable description
  • tags: Array of tags for organization

Example

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

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

Services Section

Service Types

Docker Services (type: docker)

For Docker Compose applications:

services:
  my-docker-app:
    type: "docker"
    path: "/opt/my-app"
    machine: "m1"
    compose_file: "docker-compose.yml"  # optional, defaults to docker-compose.yml
    env_file: ".env"                    # optional

Portoser will:

  • Look for docker-compose.yml in the specified path
  • Run docker-compose up -d to start the service
  • Monitor container health

Local Services (type: local)

For Python applications managed by UV:

services:
  my-python-app:
    type: "local"
    path: "/opt/my-python-app"
    machine: "m1"
    command: "uv run python main.py"
    port: 8080
    log_file: "/var/log/my-app.log"    # optional

Portoser will:

  • Set up Python virtual environment with UV
  • Run the specified command
  • Manage the process lifecycle
  • Capture logs

Native Services (type: native)

For system services (systemd/launchd):

services:
  my-system-service:
    type: "native"
    path: "/opt/my-service"
    machine: "m1"
    service_name: "my-service"
    command: "./start.sh"

Portoser will:

  • Generate systemd/launchd service file
  • Register with system service manager
  • Enable auto-start on boot

Health Checks

Define how Portoser should check if your service is healthy:

HTTP Health Check

services:
  web-app:
    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   # expected HTTP status code

TCP Health Check

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

Process Health Check

services:
  background-worker:
    health:
      type: "process"
      pid_file: "/var/run/worker.pid"
      interval: 60

Dependencies

Define service dependencies to ensure proper startup order:

services:
  web-app:
    depends_on:
      - database
      - redis
      - vault

  database:
    # No dependencies

  redis:
    # No dependencies

Portoser will:

  • Start dependencies before dependent services
  • Check dependency health before deploying
  • Prevent starting a service if dependencies are unhealthy

Environment Variables

Inline Environment Variables

services:
  my-app:
    env:
      NODE_ENV: "production"
      PORT: "3000"
      LOG_LEVEL: "info"

Environment File

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

Vault Secrets

Inject secrets from HashiCorp Vault:

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

Portoser will:

  • Fetch secrets from Vault using machine's AppRole
  • Inject as environment variables
  • Handle secret rotation

Port Mapping

services:
  web-app:
    port: 8080              # Primary port
    ports:                  # Multiple ports
      - 8080
      - 8443
    port_mapping:           # Custom mapping
      host: 80
      container: 8080

Resource Limits

For Docker services:

services:
  my-app:
    resources:
      memory: "512M"
      cpus: "0.5"
      disk: "1G"

Registry Management

Via CLI

Add a Machine

portoser machine add \
  --name m1 \
  --host 192.168.0.245 \
  --user admin \
  --platform darwin

Add a Service

portoser service add \
  --name my-app \
  --type docker \
  --path /opt/my-app \
  --machine m1

Update Service

portoser service update my-app \
  --machine mini1 \
  --port 9000

Remove Service

portoser service remove my-app

Via Web UI

  1. Navigate to Services or Machines tab
  2. Click Add New button
  3. Fill in the form
  4. Click Save

To move a service:

  1. Drag the service card
  2. Drop on target machine
  3. Confirm the move

Direct YAML Editing

You can edit registry.yml directly:

# Edit the registry
nano registry.yml

# Validate the registry
portoser registry validate

# Reload the registry
portoser registry reload

Best Practices

1. Use Descriptive Names

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

# Avoid
services:
  app1:
  service2:
  db:

2. Organize with Tags

machines:
  m1:
    tags: ["production", "macos", "high-memory"]

services:
  my-app:
    tags: ["production", "api", "customer-facing"]

3. Document Dependencies

services:
  api:
    depends_on:
      - postgres  # Required for data storage
      - redis     # Required for caching
      - vault     # Required for secrets

4. Use Health Checks

Always define health checks for critical services:

services:
  critical-api:
    health:
      type: "http"
      endpoint: "/health"
      interval: 15    # Check frequently
      retries: 2      # Fail fast

5. Vault for Secrets

Never store secrets in the registry:

# 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"]

6. Version Control

Keep your registry in version control:

git add registry.yml
git commit -m "Add new service: my-app"
git push

7. Backup Regularly

# Backup the registry
cp registry.yml registry.yml.backup

# Or use git
git add registry.yml
git commit -m "Backup registry"

Registry Validation

Validate your registry before applying changes:

# Validate syntax and structure
portoser registry validate

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

# Check for conflicts
portoser registry check-conflicts

Common validation errors:

  • Duplicate names: Service or machine names must be unique
  • Missing machines: Service references non-existent machine
  • Circular dependencies: Services depend on each other
  • Invalid types: Unknown service type
  • Port conflicts: Multiple services on same port
  • Missing required fields: host, user, platform, etc.

Advanced Features

Dynamic Port Assignment

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

Conditional Configuration

services:
  my-app:
    env:
      NODE_ENV: "{{ ENVIRONMENT }}"  # Template variable

Service Groups

groups:
  web-stack:
    - nginx
    - web-app
    - redis

services:
  nginx:
    group: "web-stack"
  web-app:
    group: "web-stack"

Deploy a group:

portoser deploy --group web-stack

Troubleshooting

Registry Not Loading

# Check syntax
portoser registry validate

# Check file permissions
ls -la registry.yml

# Check YAML syntax
yamllint registry.yml

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@machine-host

# Check machine config
portoser machine show m1

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

Next Steps