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.

nativesystemd / 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 .env or 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.