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, ornative) - Default ports, health check shape, env layout
- A starter
service_fileordocker-compose.yml - Optional placeholder code (e.g. a
main.pyfor 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.ymlforreports-apiwith sensible defaults - An
~/.portoser/templates/instances/reports-api.jsonrecord sotemplate statusknows 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 exportproduces 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 statustells 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
- Service Registry — what the registry fragment a template emits looks like in detail
- Deployment Types — the three types a template might target
- CLI Commands — full
portoser templatereference