Templates

Portoser ships a template system for generating new services from reusable starting points. Live in lib/template_commands.sh and lib/templates.sh. Use them to scaffold a Python service, a database, an MLX inference server — without copy-pasting from another project and forgetting half the wiring.

What templates are (and aren't)

Templates are registry fragments + service repo skeletons. A template knows:

  • The deployment type (docker, local, or native)
  • Default ports, health check shape, env layout
  • A starter service_file or docker-compose.yml
  • Optional placeholder code (e.g. a main.py for a FastAPI scaffold)

They are not package managers, frameworks, or Helm charts. There's no template registry to fetch from. Templates live as files inside the repo, and you author your own.

CLI

./portoser template list                       # show all available templates
./portoser template show <name>                # print template details
./portoser template use <name> <service-name>  # scaffold a new service from a template
./portoser template create <name>              # define a new template
./portoser template validate <name>            # check template structure
./portoser template export <name> <file>       # write template to a tarball
./portoser template import <file>              # load a template from a tarball
./portoser template status                     # which services were built from which templates

The dispatcher is in lib/template_commands.sh:cmd_template; subcommand implementations live next to it.

Using a template

./portoser template use python-fastapi reports-api

This produces:

  • A new directory reports-api/ with starter code (main.py, pyproject.toml, service.yml)
  • A new entry in registry.yml for reports-api with sensible defaults
  • An ~/.portoser/templates/instances/reports-api.json record so template status knows where it came from

Edit the generated files, run ./portoser registry validate, deploy:

./portoser deploy laptop reports-api

Authoring your own template

Inside the repo, drop a directory under lib/templates/ (or wherever your TEMPLATE_DIR points) with the shape:

my-template/
├── manifest.yml         # name, description, deployment_type, defaults
├── service-skeleton/    # files copied into the new service's directory
│   ├── service.yml
│   ├── main.py
│   └── pyproject.toml
└── registry-fragment.yml  # service entry merged into registry.yml

manifest.yml example:

name: python-fastapi
description: Minimal FastAPI service with a health endpoint and uv setup
deployment_type: local
defaults:
  port: 8000
  healthcheck_url: http://{hostname}/health
placeholders:
  - hostname: "{{service-name}}.internal"
  - port: "{{port}}"

./portoser template validate <name> will tell you if anything's missing before you try to use it.

Where templates earn their keep

  • Repeated service shapes. If you've spun up four FastAPI services that all health-check the same way, the fifth should come from a template.
  • Sharing a setup with a teammate. ./portoser template export produces a tarball; the other person ./portoser template imports it and now both clusters share a starting point.
  • Standardizing. When the self-healing loop's standardize step writes a playbook, it sometimes points at a template — "next time you scaffold this kind of service, use template X."

Where they don't help

  • Migrations. Changing a template doesn't retroactively change services already built from it. template status tells you what's still on an old version, but propagating updates is a manual job.
  • Complex code generation. Templates are file copies plus a few placeholder substitutions. They're not Cookiecutter; they're not Yeoman. If you need rich templating logic, use a real generator and paste the output into service-skeleton/.

Next