Skip to content

Instantly share code, notes, and snippets.

@brianredbeard
Created July 27, 2025 02:23
Show Gist options
  • Save brianredbeard/abe81de70d7fdfba08807c959f861964 to your computer and use it in GitHub Desktop.
Save brianredbeard/abe81de70d7fdfba08807c959f861964 to your computer and use it in GitHub Desktop.
Playwright MCP running under systemd via podman

About

This document outlines the configuration of a systemd unit designed to run the Playwright MCP server. The setup is opinionated, adhering to the Filesystem Hierarchy Standard (FHS) and the curated SELinux policies found in Fedora and Red Hat Enterprise Linux distributions.

Rootful Container Execution

This systemd unit runs the Playwright MCP container in "rootful" mode, meaning the container is managed by a podman process running as the root user. This is in contrast to Podman's default and more secure "rootless" mode, which allows non-privileged users to run containers. Running as root is a deliberate choice in this configuration, often necessitated by requirements like binding to privileged ports below 1024.

Networking and DNS

For seamless container communication, a dedicated container network named br-container is created using the command:

sudo podman network create br-container

When containers are connected to this user-defined bridge network, Podman provides automatic DNS-based service discovery. This functionality is handled by aardvark-dns, an authoritative DNS server that serves DNS records for containers within Podman-managed networks. This allows containers on the same network to resolve each other's IP addresses by their container names.

SELinux Configuration

When using Podman on a system with SELinux in enforcing mode, it is crucial to manage the security contexts of volumes mounted into containers.

The systemd unit file specifies the volume mount as --volume /srv/containers/%N/data:/data:Z. The :Z suffix instructs Podman to relabel the host directory (/srv/containers/playwright-mcp-server/data) with a private, unshared SELinux label (container_file_t). This makes the directory accessible only to the processes within that specific container.

The specified host path, /srv/containers/, is intentionally chosen because standard SELinux policies on Fedora and RHEL-based systems are pre-configured to handle files in this directory. As shown below, files and directories created under /srv/containers/ will automatically inherit the container_file_t context, which is necessary for container runtimes like Podman to access them.

berstuk ~ » sudo semanage fcontext -l | grep \/srv\/container
/srv/containers(/.*)?                              all files          system_u:object_r:container_file_t:s0 
/var/srv/containers(/.*)?                          all files          system_u:object_r:container_file_t:s0 

This pre-configured policy simplifies volume management, as you do not need to manually change the SELinux context of the host directory.

Setting up the directories

sudo mkdir -p /srv/containers/playwright/{conf,data}
# Setting ownership to match the uidnumber/gidnumber used in the playwright container
sudo chown -R 1000:1000 /srv/containers/playwright
sudo chmod -R g+rwX /srv/containers/playwright

Differences Between Docker and Podman

While this systemd unit is designed for Podman, it can be adapted for Docker. However, there are some key differences to be aware of:

  • Architecture: The most significant difference is that Docker uses a client-server architecture with a long-running daemon (dockerd) that manages all containers. Podman, on the other hand, is daemonless and launches containers as child processes of the user that starts them.
  • Systemd Integration: Podman offers superior integration with systemd. It includes tools like podman generate systemd to automatically create systemd unit files for containers, simplifying the process of managing containers as system services.
  • Security: Podman is generally considered more secure due to its default rootless operation. While Docker has a rootless mode, it is not the default and has some limitations.
  • CLI Compatibility: For most common commands, Podman is a drop-in replacement for Docker, meaning you can often alias docker to podman (alias docker=podman) with minimal issues.
  • Image Building: Docker has built-in image building capabilities (docker build). Podman often relies on a separate tool called Buildah for more advanced image creation, though basic podman build functionality exists.

To adapt this unit file for Docker, you would primarily need to replace the podman commands with their docker equivalents. For example, using sed 's/podman/docker/g'. However, be mindful that Docker's interaction with systemd and SELinux may require different configurations.

systemd

Installation

  1. Create the systemd Unit File: Save the provided unit file content to /etc/systemd/system/playwright-mcp-server.service. This location is the standard directory for system-wide services managed by systemd.

  2. Create the Container Data Directory: The service expects a directory on the host system to mount as a volume inside the container. Create the necessary directory structure:

    sudo mkdir -p /srv/containers/playwright-mcp-server/data

    On SELinux-enforcing systems like Fedora or RHEL, the correct container_file_t security context will be automatically applied to this directory based on existing file context rules.

  3. Create the Podman Network: For automatic DNS resolution between containers, create the specified Podman network:

    sudo podman network create br-container

    This only needs to be done once. Any subsequent containers can be attached to this same network.

  4. Reload the systemd Daemon: After creating a new unit file, you must inform systemd to scan for new or changed units:

    sudo systemctl daemon-reload

Administration and Management

Once the unit file is installed, you can manage the Playwright MCP server using standard systemctl commands.

  • Enabling the Service: To have the service start automatically at boot, you need to enable it:

    sudo systemctl enable playwright-mcp-server.service

    This creates a symbolic link from the system's service startup directory to the unit file.

  • Starting and Stopping the Service: You can manually control the service with the following commands:

    • To start the service:
      sudo systemctl start playwright-mcp-server.service
    • To stop the service:
      sudo systemctl stop playwright-mcp-server.service
  • Restarting the Service: If you make changes to the container image or need to restart the service for any reason:

    sudo systemctl restart playwright-mcp-server.service
  • Checking the Service Status: To view the current status of the service, including whether it is active, and to see the latest log entries, use:

    sudo systemctl status playwright-mcp-server.service

    This will provide a snapshot of the service's state, including process IDs and recent log output.

Viewing Logs and Troubleshooting

  • Viewing Logs: For more detailed and historical logs, use the journalctl command. To follow the logs in real-time, which is useful for debugging:

    sudo journalctl -u playwright-mcp-server.service -f

    This shows the logs specifically for this unit. Since the container is configured to use the conmon sdnotify mechanism, container logs will be directly ingested into the systemd journal.

  • Troubleshooting: If the service fails to start, begin by checking the status and logs:

    1. Run sudo systemctl status playwright-mcp-server.service to see if there are immediate error messages.
    2. Use sudo journalctl -u playwright-mcp-server.service to get a more complete picture of what might have gone wrong during the startup process. Common issues could include networking problems, SELinux permission errors, or issues with the container image itself.
    3. You can also inspect the running containers using sudo podman ps -a to see if the container is being created and what its exit status is.
# /etc/systemd/system/playwright.service
[Unit]
Description=Playwright MCP Server
Documentation=https://github.com/microsoft/playwright
After=network-online.target
Wants=network-online.target
Requires=podman.service
After=podman.service
[Service]
Type=notify
NotifyAccess=all
Restart=always
RestartSec=5
TimeoutStartSec=300
TimeoutStopSec=30
# Security hardening
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/srv/containers/%N
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK
RestrictNamespaces=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes
LockPersonality=yes
# Container management
ExecStartPre=-/usr/bin/podman rm -f %N
ExecStart=/usr/bin/podman run --rm \
--name %N \
--cidfile=%t/%n.ctr-id \
--cgroups=no-conmon \
--sdnotify=conmon \
--replace \
--volume /srv/containers/%N/data:/data:Z \
--network br-container \
--publish 8931:8931 \
mcr.microsoft.com/playwright/mcp --port 8931 --vision
ExecStop=/usr/bin/podman stop -t 10 %N
ExecStopPost=-/usr/bin/podman rm -f %N
ExecStopPost=-/bin/rm -f %t/%n.ctr-id
# Environment
Environment=PODMAN_SYSTEMD_UNIT=%n
[Install]
WantedBy=multi-user.target
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment