diff --git a/TODO b/TODO
index 7175992c055cb705f004bf208342708c1adc66ed..9d87db7d573bc49c63dcbf36b2786d3f33d6d641 100644
--- a/TODO
+++ b/TODO
@@ -1,4 +1,4 @@
-- colors
+- more colors
 - "shc CONTAINER start" vs. "shc CONTAINER shell"
 
 Albireo cleanup:
diff --git a/debian/control b/debian/control
index 1724ebcbbd60d02d956624c99e44fed49a871b74..c6a7759b1778f5604c8c55a8b153a3e827accbb7 100644
--- a/debian/control
+++ b/debian/control
@@ -10,7 +10,7 @@ Build-Depends: debhelper (>= 12.0), debhelper-compat (= 13),
 
 Package: kam-shipcat
 Architecture: any
-Depends: podman, adduser
+Depends: podman, adduser, python3-termcolor
 Suggests: rsync
 Description: The Ship's Cat manages containers
   A simple tool for managing podman containers at KAM.
diff --git a/debian/kam-shipcat.shc@.service b/debian/kam-shipcat.shc@.service
index decd960e32d94dcc66c6a91bebca8b986efd4db4..cb87fb05510467fe3a7e57da634c593b29f4368b 100644
--- a/debian/kam-shipcat.shc@.service
+++ b/debian/kam-shipcat.shc@.service
@@ -9,9 +9,8 @@ Type=forking
 TimeoutStopSec=60
 PIDFile=%t/shc/%i.pid
 
-ExecStartPre=/bin/rm -f %t/shc/%i.pid
-ExecStart=/usr/bin/podman start %i
-ExecStop=/usr/bin/podman stop --ignore %i
+ExecStart=/usr/bin/shipcat-start %i
+ExecStop=/usr/bin/shipcat-stop %i
 
 RuntimeDirectory=shc
 RuntimeDirectoryPreserve=yes
diff --git a/pyproject.toml b/pyproject.toml
index 0f2d91bb5e5c10261f1088bdc3571339659020c9..461d6428256288c8afa128c53b0b801201c88f5d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -13,6 +13,9 @@ authors = [
 maintainers = [
   {name="Martin Mareš", email="mj@ucw.cz"},
 ]
+dependencies = ["termcolor"]
 
 [project.scripts]
 shipcat = "shipcat.main:main"
+shipcat-start = "shipcat.main:main_service_start"
+shipcat-stop = "shipcat.main:main_service_stop"
diff --git a/shipcat/main.py b/shipcat/main.py
index 6399d809ea245bc6bedd313d2788bf9238463ac9..1b6d02327c65af463aaa99345b9dd2a534dd9306 100755
--- a/shipcat/main.py
+++ b/shipcat/main.py
@@ -7,40 +7,25 @@ import os
 from pathlib import Path
 from pwd import getpwnam
 import socket
-import subprocess
 import sys
-from typing import NoReturn, List, Optional
+from typing import List, Optional
 
 from shipcat.config import GlobalConfig, ContainerConfig, ConfigError
+from shipcat.util import die, verbose, set_verbose, run_command
 
 
 config: GlobalConfig
-verbose: int
-
-
-def die(msg: str) -> NoReturn:
-    print(msg, file=sys.stderr)
-    sys.exit(1)
 
 
 def progress(msg: str) -> None:
     print(msg, file=sys.stderr, end="", flush=True)
 
 
-def run_command(cmd: List[str], *args, **kwargs) -> None:
-    if verbose:
-        print('RUN: ' + " ".join(cmd))
-    res = subprocess.run(cmd, *args, **kwargs)
-    if res.returncode != 0:
-        sys.exit(res.returncode)
-
-
 def load_config() -> GlobalConfig:
     try:
         return GlobalConfig.load('/etc/shipcat/shipcat.toml')
     except ConfigError as e:
-        print(e, file=sys.stderr)
-        sys.exit(1)
+        die(str(e))
 
 
 def setup_container(args: argparse.Namespace, require_root: bool, container: Optional[str] = None) -> ContainerConfig:
@@ -311,6 +296,33 @@ def cmd_list(args: argparse.Namespace) -> None:
                 print(cc.container)
 
 
+def main_service_start():
+    # Service helper for service start, called by root
+
+    if len(sys.argv) != 2:
+        die("Expected arguments: container")
+
+    config = load_config()
+    cc = ContainerConfig.load(config, sys.argv[1])
+
+    pid_file = f'/run/shc/{cc.container}.pid'
+    Path(pid_file).unlink(missing_ok=True)
+
+    run_command(['podman', 'start', cc.container])
+
+
+def main_service_stop():
+    # Service helper for service stop, called by root
+
+    if len(sys.argv) != 2:
+        die("Expected arguments: container")
+
+    config = load_config()
+    cc = ContainerConfig.load(config, sys.argv[1])
+
+    run_command(['podman', 'stop', '--ignore', cc.container])
+
+
 def parse_int_list(s: str) -> List[int]:
     return list(map(int, s.split(',')))
 
@@ -382,8 +394,8 @@ def main() -> None:
         'stop': cmd_stop,
     }
 
-    global config, verbose
+    global config
     config = load_config()
-    verbose = args.verbose
+    set_verbose(args.verbose)
 
     actions[args.action](args)
diff --git a/shipcat/util.py b/shipcat/util.py
new file mode 100644
index 0000000000000000000000000000000000000000..178448af9ccb3621a5c270df547f18d04d3755d3
--- /dev/null
+++ b/shipcat/util.py
@@ -0,0 +1,52 @@
+# Utility functions
+
+import subprocess
+import sys
+from termcolor import cprint
+from typing import NoReturn, List
+
+
+verbose = 0
+
+
+def die(msg: str) -> NoReturn:
+    cprint(msg, 'red', file=sys.stderr)
+    sys.exit(1)
+
+
+def fatal(msg: str) -> NoReturn:
+    die('FATAL: ' + msg)
+
+
+def error(msg: str) -> NoReturn:
+    die('ERROR: ' + msg)
+
+
+def warn(msg: str) -> None:
+    cprint('WARNING: ' + msg, 'red', file=sys.stderr)
+
+
+def progress(msg: str) -> None:
+    cprint(msg, 'green', file=sys.stderr)
+
+
+def note(msg: str) -> None:
+    cprint(msg, 'yellow', file=sys.stderr)
+
+
+def debug(msg: str, level: int = 1) -> None:
+    if verbose >= level:
+        print(msg)
+
+
+def set_verbose(level: int) -> None:
+    global verbose
+    verbose = level
+
+
+def run_command(cmd: List[str], *args, **kwargs) -> None:
+    if verbose:
+        print('RUN: ' + " ".join(cmd))
+    res = subprocess.run(cmd, *args, **kwargs)
+    if res.returncode != 0:
+        sys.exit(res.returncode)