Deployment Types
Portoser supports three deployment types, declared per service in registry.yml:
| Type | When to use | Lives in |
|---|---|---|
docker |
The default. Containerized service via Docker Compose. | lib/docker.sh |
local |
A Python project that should run as a long-lived process under a process supervisor, not in a container. | lib/local.sh |
native |
A service installed and run by the host's native service manager — systemctl on Linux, launchctl on macOS. |
lib/native.sh |
You can mix all three in the same registry. Portoser will pick the right path per service based on deployment_type.
docker — Compose services
services:
api:
deployment_type: docker
machine: worker-1
repo: ./api
compose_file: docker-compose.yml
health_check:
type: http
url: http://localhost:8080/healthz
Portoser ships the repo to the target machine over SSH, runs docker compose up -d, and watches health. Caddy is reconfigured automatically if a hostname is declared.
local — Python with uv
services:
worker:
deployment_type: local
machine: mini-1
repo: ./worker
entrypoint: python -m worker.main
health_check:
type: process
pid_file: ~/.portoser/run/worker.pid
Portoser installs dependencies with uv sync, starts the entrypoint as a backgrounded process, writes a PID file, and tails the log to ~/.portoser/logs/<service>.log. uv is used for reproducible installs via uv.lock, not for runtime speedups.
native — systemd / launchd
services:
postgres:
deployment_type: native
machine: db-host
native_unit: postgresql.service # Linux (systemctl)
# or:
# native_label: org.postgresql.postgres # macOS (launchctl)
health_check:
type: http
url: http://localhost:5432
Portoser asks the host's native service manager to start, stop, and report status. It does not install the package for you — native services assume the host already has them set up. Use native for services like databases or DNS that are typically managed at the OS layer.
The platform abstraction in lib/platform/detector.sh chooses systemctl or launchctl automatically.
Choosing a type
A simple rule of thumb:
- Want it isolated and reproducible? →
docker - It's your own Python code that benefits from running outside a container (fast iteration, host hardware access, GPU)? →
local - It's an OS-level service the host already manages? →
native
You can change the type by editing the registry and redeploying. State migration between types is your responsibility.
What's identical across types
- Health checks (HTTP, TCP, process, exec)
- Dependency declarations (
depends_on) - Environment variable loading from
.envor Vault - The deployment history and rollback workflow
- The self-healing loop
So the choice of type doesn't lock you out of any feature. It only changes how the process gets started and stopped.