systemd portable services let you package entire applications, including their systemd unit files, into a single, self-contained directory structure that can be easily moved between machines.
Let’s see one in action. Imagine we have a simple Python web application.
import http.server
import socketserver
PORT = 8000
class Handler(http.server.SimpleHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header("Content-type", "text/plain")
self.end_headers()
self.wfile.write(b"Hello from portable service!")
with socketserver.TCPServer(("", PORT), Handler) as httpd:
print(f"Serving on port {PORT}")
httpd.serve_forever()
Now, we’ll create the directory structure that systemd will recognize as a portable service.
mkdir -p my-portable-app/usr/lib/systemd/system
mkdir -p my-portable-app/opt/my-app
We’ll place our Python script inside the application directory:
my-portable-app/opt/my-app/app.py:
# (same content as above)
And the systemd unit file will go into the systemd system directory. The key here is the [Install] section. It defines how the service should be enabled.
my-portable-app/usr/lib/systemd/system/my-portable-app.service:
[Unit]
Description=My Simple Portable Web App
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
ExecStart=/usr/bin/python3 /opt/my-app/app.py
WorkingDirectory=/opt/my-app
StandardOutput=journal
StandardError=journal
Restart=on-failure
[Install]
WantedBy=multi-user.target
Notice ExecStart points to the Python script within our opt directory. WorkingDirectory is also set to this location.
To deploy this, we simply copy the entire my-portable-app directory to a target machine. Let’s say we put it at /opt/portable-services/my-portable-app.
On the target machine, we tell systemd about this new portable service. We use systemctl enable with a path argument.
sudo systemctl enable /opt/portable-services/my-portable-app/usr/lib/systemd/system/my-portable-app.service
This command doesn’t copy the unit file; it links to it. systemd now knows about my-portable-app.service.
Then, we start it:
sudo systemctl start my-portable-app.service
You can verify it’s running:
sudo systemctl status my-portable-app.service
And check its output:
sudo journalctl -u my-portable-app.service -f
You should see "Serving on port 8000" and be able to curl localhost:8000 to get "Hello from portable service!".
The magic is that systemd treats this as a regular service, but the unit file and its dependencies are all bundled together. When you copy the my-portable-app directory, you’re moving the entire application and its service definition. This is incredibly useful for creating self-contained applications that can be deployed with minimal configuration on the host system. The [Install] section, specifically WantedBy=multi-user.target, ensures that when enabled, the service is automatically started when the system reaches the multi-user runlevel, just like any other systemd service.
The next step is to explore how to make these portable services dynamic, allowing them to be started and stopped based on specific conditions or events, rather than just at boot.