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