Service Resource¶
Manage system services (systemd, launchctl) and service lifecycle.
Overview¶
The Service resource handles service management:
- Start and stop services
- Enable and disable services at boot
- Automatic reload on configuration changes
- Automatic restart on binary updates
- Cross-platform support (Linux systemd, macOS launchctl)
Supported Service Managers¶
| Platform | Service Manager | Start/Stop | Enable/Disable | Reload | Restart |
|---|---|---|---|---|---|
| Linux | systemd | Yes | Yes | Yes | Yes |
| macOS | launchctl | Yes | Limited | No | Yes |
Basic Usage¶
Start Service¶
Enable Service at Boot¶
Stop Service¶
Disable Service¶
Parameters¶
name¶
Required. Service name as recognized by the service manager.
On systemd, this corresponds to the unit name (e.g., nginx.service).
running¶
Whether the service should be running.
True: Service must be runningFalse: Service must be stoppedNone: No running state enforcement (default)
# Ensure service is running
Service("nginx", running=True)
# Ensure service is stopped
Service("apache2", running=False)
enabled¶
Whether the service should be enabled at boot.
True: Service starts at bootFalse: Service does not start at bootNone: No boot state enforcement (default)
reload_on¶
List of resources that trigger a service reload when changed.
nginx_conf = File("/etc/nginx/nginx.conf", source="./nginx.conf")
Service("nginx", running=True, reload_on=[nginx_conf])
When nginx_conf changes, the service is reloaded (not restarted).
restart_on¶
List of resources that trigger a service restart when changed.
app_binary = File("/usr/local/bin/app", source="./app")
Service("app", running=True, restart_on=[app_binary])
When app_binary changes, the service is restarted.
Automatic Reload and Restart¶
Services can automatically reload or restart when dependencies change.
Reload on Configuration Change¶
Use reload_on for configuration file changes. This reloads the service without dropping connections.
from cook import File, Service
# Nginx configuration
nginx_conf = File("/etc/nginx/nginx.conf", source="./nginx.conf")
# Reload nginx when config changes
Service("nginx", running=True, enabled=True, reload_on=[nginx_conf])
Workflow:
1. Cook detects nginx.conf changed
2. Cook applies file changes
3. Cook reloads nginx service (graceful reload)
Restart on Binary Update¶
Use restart_on for binary or critical file changes. This fully restarts the service.
from cook import File, Service
# Application binary
app_binary = File("/opt/apps/myapp/app", source="./app", mode=0o755)
# Application service file
app_service = File(
"/etc/systemd/system/myapp.service",
source="./myapp.service"
)
# Restart when binary or service file changes
Service("myapp", running=True, enabled=True, restart_on=[app_binary, app_service])
Workflow:
1. Cook detects app binary changed
2. Cook applies file changes
3. Cook restarts myapp service (full restart)
Multiple Triggers¶
Combine reload and restart triggers:
from cook import File, Service
# Configuration files
nginx_conf = File("/etc/nginx/nginx.conf", source="./nginx.conf")
site_conf = File("/etc/nginx/sites-available/mysite", source="./mysite.conf")
# Binary files
nginx_binary = File("/usr/sbin/nginx", source="./nginx", mode=0o755)
# Reload on config changes, restart on binary changes
Service(
"nginx",
running=True,
enabled=True,
reload_on=[nginx_conf, site_conf],
restart_on=[nginx_binary]
)
Examples¶
Web Server¶
from cook import Package, File, Service
# Install nginx
Package("nginx")
# Configure nginx
nginx_conf = File("/etc/nginx/nginx.conf", source="./nginx.conf")
site_conf = File("/etc/nginx/sites-available/default", source="./default.conf")
# Manage service
Service("nginx", running=True, enabled=True, reload_on=[nginx_conf, site_conf])
Application Server¶
from cook import File, Service
# Application files
app_dir = File("/opt/apps/myapp", ensure="directory", mode=0o755)
app_binary = File("/opt/apps/myapp/app", source="./app", mode=0o755)
app_config = File("/opt/apps/myapp/config.yml", source="./config.yml")
# Systemd service file
service_file = File(
"/etc/systemd/system/myapp.service",
template="./myapp.service.j2",
vars={"user": "app", "workdir": "/opt/apps/myapp"}
)
# Manage service
Service(
"myapp",
running=True,
enabled=True,
reload_on=[app_config],
restart_on=[app_binary, service_file]
)
Database Server¶
from cook import Package, Service
# Install PostgreSQL
Package("postgresql")
# Ensure service is running and enabled
Service("postgresql", running=True, enabled=True)
Docker Daemon¶
from cook import Package, File, Service
# Install Docker
Package("docker", packages=["docker-ce", "docker-ce-cli", "containerd.io"])
# Docker daemon configuration
daemon_config = File(
"/etc/docker/daemon.json",
source="./docker-daemon.json",
mode=0o644
)
# Manage Docker service
Service("docker", running=True, enabled=True, restart_on=[daemon_config])
Disable Unwanted Service¶
Systemd Service Files¶
Create custom systemd services with File resource:
Simple Service¶
Template: myapp.service.j2
[Unit]
Description=My Application
After=network.target
[Service]
Type=simple
User={{ user }}
WorkingDirectory={{ workdir }}
ExecStart={{ exec_start }}
Restart=always
[Install]
WantedBy=multi-user.target
Cook configuration:
from cook import File, Service
# Create service file
service_file = File(
"/etc/systemd/system/myapp.service",
template="./myapp.service.j2",
vars={
"user": "app",
"workdir": "/opt/apps/myapp",
"exec_start": "/opt/apps/myapp/start.sh"
}
)
# Reload systemd daemon when service file changes
# (systemd-specific - requires custom Exec resource)
# Manage service
Service("myapp", running=True, enabled=True, restart_on=[service_file])
Service with Environment Variables¶
Template: myapp-env.service.j2
[Unit]
Description=My Application
After=network.target
[Service]
Type=simple
User={{ user }}
WorkingDirectory={{ workdir }}
Environment="PORT={{ port }}"
Environment="LOG_LEVEL={{ log_level }}"
ExecStart={{ exec_start }}
Restart=always
[Install]
WantedBy=multi-user.target
Cook configuration:
File(
"/etc/systemd/system/myapp.service",
template="./myapp-env.service.j2",
vars={
"user": "app",
"workdir": "/opt/apps/myapp",
"exec_start": "/opt/apps/myapp/app",
"port": 3000,
"log_level": "info"
}
)
Service("myapp", running=True, enabled=True)
Idempotency¶
Service resources are idempotent. Running the same configuration multiple times produces the same result.
Service("nginx", running=True, enabled=True)
# First run: Starts and enables nginx
# Second run: Detects nginx already running and enabled, no changes
Service state changes are detected:
Service("nginx", running=True)
# If nginx is stopped, Cook starts it
# If nginx is running, Cook makes no changes
Platform-Specific Behavior¶
Linux (systemd)¶
Equivalent to:
Check status:
Reload:
Restart:
macOS (launchctl)¶
Equivalent to:
Note: macOS service management differs from Linux. Services are defined in plist files in /Library/LaunchDaemons/ or ~/Library/LaunchAgents/.
Common Patterns¶
Web Application Stack¶
from cook import Package, File, Service
# Install packages
Package("nginx")
Package("postgresql")
# Nginx configuration
nginx_conf = File("/etc/nginx/nginx.conf", source="./nginx.conf")
# Services
Service("postgresql", running=True, enabled=True)
Service("nginx", running=True, enabled=True, reload_on=[nginx_conf])
Systemd Socket Activation¶
# Socket file
File(
"/etc/systemd/system/myapp.socket",
source="./myapp.socket"
)
# Service file
File(
"/etc/systemd/system/myapp.service",
source="./myapp.service"
)
# Manage socket (service starts on demand)
Service("myapp.socket", running=True, enabled=True)
Service with Health Check¶
from cook import File, Service, Exec
# Service files
app_binary = File("/opt/apps/myapp/app", source="./app", mode=0o755)
# Start service
Service("myapp", running=True, enabled=True, restart_on=[app_binary])
# Health check (verify service is responding)
Exec(
"health-check",
command="curl -f http://localhost:3000/health || exit 1",
retry=3
)
Security Considerations¶
Service User¶
Run services with dedicated users, not root:
Service Hardening¶
Use systemd hardening features:
[Service]
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/apps/myapp/data
Service Restart Limits¶
Prevent restart loops:
Troubleshooting¶
Service Fails to Start¶
Check service status:
Verify service file syntax:
Service Not Enabled¶
Ensure service file is installed before enabling:
service_file = File("/etc/systemd/system/myapp.service", source="./myapp.service")
# systemd requires daemon-reload after service file changes
# This can be done with Exec resource
Service("myapp", enabled=True)
Reload Not Working¶
Not all services support reload. Use restart instead:
Limitations¶
macOS launchctl¶
- No direct enable/disable support (services are enabled by plist file presence)
- No reload operation (use restart)
- Service names follow reverse-domain format (e.g.,
com.example.myapp)
Service Dependencies¶
Cook does not automatically handle systemd dependencies (After, Requires, Wants). Define these in the service unit file.
API Reference¶
See cook/resources/service.py for complete source code and implementation details.