Table of Contents
- Understanding Systemd Service Files (A Quick Recap)
- What Are Systemd Service Templates?
- Key Components of a Service Template
- Step-by-Step: Creating Your First Service Template
- Managing Template Instances
- Advanced Use Cases
- Troubleshooting Common Issues
- Conclusion
- References
1. Understanding Systemd Service Files (A Quick Recap)
Before diving into templates, let’s recap the basics of a systemd service file. A .service file is a plaintext configuration file that defines how a service should start, stop, and behave. It typically includes three main sections:
[Unit]: Metadata about the service (e.g., description, dependencies).[Service]: Core service configuration (e.g., executable path, user, restart policy).[Install]: Installation details (e.g., which target to start the service in).
Example of a basic static service file (myapp.service):
[Unit]
Description=My Static Node.js App
After=network.target
[Service]
User=ubuntu
ExecStart=/usr/bin/node /opt/myapp/app.js --port 3000
Restart=always
[Install]
WantedBy=multi-user.target
This works for a single instance, but what if you need to run the same app on ports 3000, 3001, and 3002? Without templates, you’d need three separate .service files—duplicating most of the configuration. Templates solve this problem.
2. What Are Systemd Service Templates?
A systemd service template is a blueprint for creating multiple service instances. Instead of hardcoding values like ports or config paths, you use instance specifiers (e.g., %i) to dynamically inject values when creating an instance.
- Template File: A special
.servicefile named with a@(e.g.,[email protected]). The@denotes it as a template. - Instance: A running service created from the template, identified by a unique name (e.g.,
[email protected], where3000is the instance identifier).
Why Use Templates?
- Maintainability: Update one template instead of dozens of duplicate service files.
- Scalability: Spin up new instances with a single command (e.g.,
systemctl start myapp@3002). - Flexibility: Dynamically configure instances with unique ports, paths, or environment variables.
3. Key Components of a Service Template
Template File Naming Convention
A template file must end with @.service (e.g., [email protected], [email protected]). The @ is critical—systemd uses it to distinguish templates from regular services.
Instance Specifiers
Specifiers are placeholders in the template that systemd replaces with the instance identifier when an instance is started. The most common specifiers are:
| Specifier | Description |
|---|---|
%i | The instance identifier (escaped to be filesystem-safe; replaces slashes with hyphens). |
%I | The unescaped instance identifier (use for paths with special characters). |
%h | The home directory of the user running the service. |
%m | The machine ID of the host. |
For most use cases, %i (escaped identifier) is sufficient. For example, if you start [email protected], %i becomes 3000.
4. Step-by-Step: Creating Your First Service Template
Let’s create a template for a Node.js app that runs on a user-specified port. We’ll use %i to inject the port dynamically.
Example: A Dynamic Node.js App Template
Step 1: Prepare the App
Assume we have a simple Node.js app (/opt/myapp/app.js) that accepts a --port argument:
const http = require('http');
const { port } = require('minimist')(process.argv.slice(2));
http.createServer((req, res) => {
res.write(`Hello from port ${port}!`);
res.end();
}).listen(port, () => {
console.log(`App running on port ${port}`);
});
Step 2: Create the Template File
Create /etc/systemd/system/[email protected] (template file). Use %i for the port:
[Unit]
Description=My Dynamic Node.js App (Port %i) # %i shows the instance port in logs
After=network.target
[Service]
User=ubuntu
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/node app.js --port %i # %i is replaced with the instance identifier (e.g., 3000)
Restart=always
StandardOutput=journal # Log to systemd journal
StandardError=journal
[Install]
WantedBy=multi-user.target
Step 3: Start an Instance
To run the app on port 3000, start the instance [email protected]:
sudo systemctl start [email protected]
Step 4: Verify the Instance
Check the status:
sudo systemctl status [email protected]
Output should show the service is running, with %i replaced by 3000:
● [email protected] - My Dynamic Node.js App (Port 3000)
Loaded: loaded (/etc/systemd/system/[email protected]; disabled; vendor preset: enabled)
Active: active (running) since Wed 2024-05-20 12:00:00 UTC; 5s ago
Main PID: 12345 (node)
Tasks: 10 (limit: 4915)
Memory: 10.0M
CGroup: /system.slice/system-myapp.slice/[email protected]
└─12345 /usr/bin/node app.js --port 3000
Step 5: View Logs
Check logs for the instance with journalctl:
journalctl -u [email protected] -f # -f for "follow" (live logs)
You’ll see:
May 20 12:00:00 server node[12345]: App running on port 3000
Step 6: Start More Instances
Spin up another instance on port 3001 with:
sudo systemctl start [email protected]
Now you have two instances (3000 and 3001) running from the same template!
5. Managing Template Instances
Systemd treats each instance as a unique service. Use these commands to manage instances:
| Task | Command Example |
|---|---|
| Start an instance | sudo systemctl start [email protected] |
| Stop an instance | sudo systemctl stop [email protected] |
| Restart an instance | sudo systemctl restart [email protected] |
| Enable an instance (start on boot) | sudo systemctl enable [email protected] |
| Disable an instance (stop on boot) | sudo systemctl disable [email protected] |
| List all running instances | sudo systemctl list-units --type=service --full --all 'myapp@*.service' |
Removing an Instance
To fully remove an instance (e.g., 3000):
- Stop and disable it:
sudo systemctl stop [email protected] sudo systemctl disable [email protected] - (Optional) Delete instance-specific files (e.g., logs, configs).
6. Advanced Use Cases
Templates shine in complex scenarios where you need dynamic, scalable service management. Here are three powerful examples:
Dynamic Configuration with Environment Files
Use %i to load instance-specific environment files. For example, create /etc/myapp/%i.conf for each instance:
Template ([email protected]):
[Service]
EnvironmentFile=/etc/myapp/%i.conf # Loads /etc/myapp/3000.conf for instance 3000
ExecStart=/usr/bin/node app.js --port ${PORT} # Use variable from env file
Instance 3000’s config (/etc/myapp/3000.conf):
PORT=3000
LOG_LEVEL=info
API_KEY=abc123
Orchestrating Docker Containers
Manage multiple Docker containers with a single template. For example, run Nginx containers on different ports:
Template ([email protected]):
[Unit]
Description=Nginx Container on Port %i
After=docker.service
Requires=docker.service
[Service]
ExecStart=/usr/bin/docker run --name nginx-%i -p %i:80 nginx:alpine
ExecStop=/usr/bin/docker stop nginx-%i
ExecStopPost=/usr/bin/docker rm nginx-%i
Restart=always
Start an instance on port 8080:
sudo systemctl start [email protected]
Dependency Management for Instances
Ensure instances start after their dependencies. For example, a template for a database replica might depend on a primary instance:
[Unit]
Description=PostgreSQL Replica %i
After[email protected] # Wait for primary to start
Requires[email protected]
[Service]
ExecStart=/usr/bin/postgres -D /var/lib/postgres/%i # Unique data directory per replica
7. Troubleshooting Common Issues
| Issue | Cause & Fix |
|---|---|
%i not replaced in ExecStart | Forgetting the @ in the template filename (e.g., myapp.service instead of [email protected]). Rename the template. |
| Instance fails to start | Missing instance-specific files (e.g., /etc/myapp/%i.conf). Check journalctl -u myapp@3000 for errors. |
| Special characters in instance names | Use %I instead of %i to avoid escaping (e.g., myapp@user:admin.service with %I becomes user:admin). |
| Logs not showing | Use journalctl -u myapp@3000 (specify the instance). |
8. Conclusion
Systemd service templates transform how you manage multiple instances of the same service. By replacing static configurations with dynamic specifiers, you reduce redundancy, improve scalability, and simplify maintenance. Whether you’re running microservices, databases, or containers, templates are a must-know tool for modern Linux system administration.
Start small (e.g., a Node.js app with dynamic ports) and gradually adopt templates for more complex workflows—your future self (and your server) will thank you!