Skip to content

services.yml - Declarative Service Definitions Guide

Introduction

services.yml is a Procfile-like YAML file that declares which processes your application needs to run as systemd services. Place it at .muppy/services.yml in your git repository, and Muppy automatically creates, configures, and manages the corresponding systemd units on your App Server.

Key Characteristics

  • Declarative: Describe what to run, Muppy handles how (systemd unit files, enable, start)
  • Idempotent sync: Re-deploying updates existing services, creates new ones, removes deleted ones
  • Auto-detected: Processed automatically during server provisioning (except dev-category servers)
  • Procfile-inspired: Minimal syntax — command is the only required key

Philosophy: Source of Truth

Private Repositories (you can commit)

Commit .muppy/services.yml directly in your repository. It is automatically processed during server provisioning. The repository is the source of truth.

Open Source / External Repositories (you cannot commit)

Use MCP tools or the GUI "Deploy Services" button to trigger sync manually. The AI agent can write the file via mgx_exec and deploy it.


Quick Start

1. Create the file

Add .muppy/services.yml to your repository:

services:
  gui:
    command: bin/mpy-srv --workers=4
  cron:
    command: bin/mpy-srv imq-worker --queue='*' 
    restart: always

That's it. command is the only required key.

2. Enable "Manage Services"

In the Services tab, ensure the "Manage Services" toggle is ON. For dev-category servers, it defaults to OFF — flip it to enable service management.

3. Deploy

  • Automatic: Happens during server provisioning/setup (non-dev qualifiers)
  • Manual: Click "Deploy Services" in the Services tab, or call mgx_configure_systemd_units_from_definitions() via MCP

4. Verify

Check the Services tab — your services should appear with active_state: active and sub_state: running.

If a service failed, click "Logs" to view journalctl output and diagnose the issue.


Complete Reference

Service Keys

Each key under services: becomes a systemd unit. The key name is the unit code (e.g., gui, cron, worker).

Constraints on unit codes:

  • Only lowercase alphanumeric characters: [a-z0-9]
  • Must be unique within the file
  • Generated unit name: mpy_{type}_appsrv_{identifier}_{unit_code}.service

Supported Configuration Keys

Key Required Default Description
command Yes Executable + arguments (relative to app directory)
type No service service (long-running) or timer (scheduled)
restart No on-failure Systemd Restart= directive
restart_sec No 1s Systemd RestartSec= directive — delay before restarting
env No EnvironmentFile=/etc/muppy.env List of Environment= or EnvironmentFile= directives

Restart Policies

Value Description
on-failure Restart only on unclean exit (default)
always Always restart, regardless of exit status
no Never restart
on-abnormal Restart on signal, timeout, or watchdog
on-success Restart only on clean exit
on-abort Restart only on signal
on-watchdog Restart only on watchdog timeout

Restart Delay (restart_sec)

restart_sec maps to systemd's RestartSec= directive — the delay before restarting a crashed service. Muppy defaults to 1s instead of systemd's native ~100ms because the native default causes restart storms when ports take longer to free (common on JVM, Python ASGI, and Node apps).

services:
  java_api:
    command: /usr/bin/java -jar build/libs/app.jar
    restart: always
    restart_sec: 20s          # JVM apps: socket often held for several seconds
  quick_worker:
    command: ./worker.sh
    restart_sec: 1s           # default — fast workers can restart quickly

Accepted values: 500ms, 1s, 20s, 2min, or any systemd time span.

Tip

If a service enters auto-restart then failed with "Address already in use", bump restart_sec to 10s or 20s to give the old socket time to release.

Environment Directives

The env key accepts a list of systemd environment directives:

services:
  myapp:
    command: python app.py
    env:
      - "EnvironmentFile=/etc/muppy.env"
      - "Environment=NODE_ENV=production"
      - "Environment=PORT=8080"

Info

EnvironmentFile=/etc/muppy.env is always included even if not explicitly listed. This provides PostgreSQL connection variables (PGHOST, PGUSER, PGPASSWORD, PGDATABASE, PGPORT) and other Muppy environment variables.

Command Rules

  • First token = launcher script, rest = options.
  • Path resolution:
    • Absolute path (/usr/bin/java): used as-is. Required for system binaries (java, python, node, ruby, …) — systemd's PATH does not reliably include /usr/bin for non-login services. Use which java on the target server to find the right path. A bare java will fail at startup with status=203/EXEC.
    • Explicit relative (./bin/start.sh): used as-is, resolved from the app directory (the repo root, set as systemd WorkingDirectory).
    • Bare relative (bin/start.sh): auto-prefixed with ./ and resolved from the app directory. Same effective behavior as the explicit form.
  • Shell metacharacters are forbidden: ;, |, &, `, $()
  • The service runs as the App Server's system user (not root)
services:
  web:
    command: bin/mpy-srv --workers=4         # bare relative -> auto ./
  worker:
    command: ./scripts/worker.sh             # explicit relative
  java_app:
    command: /usr/bin/java -jar build/libs/app.jar   # absolute (required for java)

Merge Behavior

  • Definitions from services.yml (source=services_yml) never override admin-defined units (source=manual)
  • Removing a service from the file stops and removes the corresponding systemd unit on the next sync
  • Re-adding a removed service recreates it

Timer Services

Timers are scheduled tasks — the systemd equivalent of cron jobs. Set type: timer and provide at least one scheduling directive.

Example

services:
  backup:
    command: scripts/backup.sh
    type: timer
    on_calendar: "*-*-* 02:00:00"
    persistent: true

  cleanup:
    command: scripts/cleanup.sh
    type: timer
    on_calendar: "weekly"

Timer-Specific Keys

Key Default Description
on_calendar Systemd calendar expression (see below)
on_boot_sec Run N time after boot (e.g., 5min)
on_unit_active_sec Run periodically after last activation (e.g., 1h)
on_unit_inactive_sec Run after last deactivation
persistent false Catch up missed runs when system was off
accuracy_sec 1min Timer accuracy (lower = more precise)
randomized_delay_sec Add random delay to prevent thundering herd

Warning

At least one scheduling directive (on_calendar, on_boot_sec, on_unit_active_sec, or on_unit_inactive_sec) is required for timers.

OnCalendar Syntax

Expression Meaning
daily Every day at midnight
hourly Every hour
weekly Every Monday at midnight
*-*-* 02:30:00 Every day at 02:30
Mon *-*-* 09:00:00 Every Monday at 09:00
*-*-01 00:00:00 First day of every month

Workflow: Setting Up Services

Step 1: Analyze Your App

Identify what processes your application needs:

  • Web server: Serves HTTP requests (e.g., Odoo GUI workers, Node.js server)
  • Background workers: Process async tasks (e.g., Celery, Odoo CRON workers)
  • Scheduled tasks: Periodic jobs (e.g., backups, cleanups)

Step 2: Write services.yml

Create .muppy/services.yml in your repository. Start minimal:

services:
  web:
    command: bin/start-web.sh --port 8000

Add more services as needed.

Step 3: Enable and Deploy

First, ensure the "Manage Services" toggle is ON in the Services tab (OFF by default for dev servers).

Then choose one:

  • GUI: Open your App Server → Services tab → click "Deploy Services"
  • MCP: Call mgx_configure_systemd_units_from_definitions() on your dev server
  • Automatic: Happens during provisioning/setup (non-dev qualifiers only)

Step 4: Verify

In the Services tab:

  • Check active_state = active and sub_state = running for each service
  • Click "Refresh All" to update status from the host

Step 5: Troubleshoot

If a service is failed or inactive:

  1. Click "Logs" on the failed service to view journalctl output
  2. Fix the issue (command path, permissions, missing dependencies)
  3. Edit .muppy/services.yml if needed
  4. Click "Deploy Services" again to redeploy

AI Agent Workflow

When working with an AI assistant (via MCP), the typical workflow is:

User: "I want my app to run as a background service"

1. AI reads dev_server → checks activate_systemd_services
2. If False: write_objects(values={"activate_systemd_services": True})
3. AI checks: mgx_exec(command="cat .muppy/services.yml")
4. AI writes the file:
   mgx_exec(command="mkdir -p .muppy && cat > .muppy/services.yml << 'EOF'
   services:
     web:
       command: bin/start-web.sh --workers=4
   EOF")
5. AI deploys: mgx_configure_systemd_units_from_definitions()
6. AI verifies: reads systemd_unit_ids → checks active_state
7. If failed: mgx_journalctl() → diagnoses → fixes → redeploys

The AI can read the full format reference from mpy.template with code mpy-services-yaml-default.


UI Reference: Services Tab

Manage Services Toggle

The "Manage Services" toggle (activate_systemd_services) is the master switch. When OFF, all controls below are hidden and no service operations are possible. For dev-category servers, it defaults to OFF — enable it to start managing services.

Buttons

Button Action
Start All Start all registered systemd units
Stop All Stop all registered systemd units
Restart All Restart all registered systemd units
Refresh All Refresh status from host (systemctl show)
Deploy Services Sync .muppy/services.yml + deploy all definitions
Import custom Services Discover and import existing host services
Documentation Open this guide in a new tab

Running Services List

Shows all registered mpy.systemd_unit records with:

  • Status indicators (active_state, sub_state with color badges)
  • Per-service buttons: Start, Stop, Restart, Refresh, Logs, Unregister

Service Definitions List

Shows mpy.dev_server_systemd_unit_definition records — what should be deployed:

  • Source badge: services_yml (from .muppy/services.yml) or manual (from App Definition/admin)
  • Unit code, class, launcher script, options, restart policy

Relationship with mpy_setup.sh

Both files live in .muppy/ but serve different purposes:

mpy_setup.sh services.yml
Purpose Install system dependencies Declare running processes
When Runs once during provisioning Synced at each deploy
Examples apt-get install nodejs, pip install, Redis setup bin/mpy-srv --workers=4, python worker.py
Format Bash script (imperative) YAML (declarative)
Idempotent Script must check before acting Sync is inherently idempotent

Typical flow: mpy_setup.sh installs Node.js → services.yml declares node server.js as a service.


Examples

Odoo Application (GUI + CRON)

services:
  gui:
    command: bin/mpy-srv --workers=4
    restart: on-failure
  cron:
    command: bin/mpy-srv imq-workert --queue='*'
    restart: always

Node.js Application (Web + Worker)

services:
  web:
    command: node server.js
    restart: on-failure
    env:
      - "Environment=NODE_ENV=production"
      - "Environment=PORT=8080"
  worker:
    command: node worker.js
    restart: always

Python Application (API + Celery + Scheduled Cleanup)

services:
  api:
    command: python -m uvicorn main:app --host 0.0.0.0 --port 8000
    restart: on-failure
  celery:
    command: celery -A tasks worker --loglevel=info
    restart: always
  cleanup:
    command: python scripts/cleanup.py
    type: timer
    on_calendar: "*-*-* 03:00:00"
    persistent: true