Replacing socat with systemd-socket-proxyd
Bottlerocket v11.0.0 dropped the socat package from bottlerocket-core-kit (#742). If you had a service that relied on socat to bridge a Unix Domain Socket (UDS) to a TCP port, you need a replacement. This post shows how to use systemd-socket-proxyd instead.
Why socat was used
soci-snapshotter exposes its metrics endpoint over a Unix Domain Socket at /run/soci-snapshotter-grpc/metrics.sock. The OpenTelemetry Collector's Prometheus receiver, however, expects an HTTP endpoint with a TCP address. The two can't talk directly.
socat was the classic fix: run it as a service that listens on a TCP port and forwards traffic to the UDS.
1# soci-snapshotter-metrics.service (socat approach)
2[Unit]
3Description=Create a UNIX socket and connect to the TCP endpoint from which Prometheus scrapes soci metrics.
4After=soci-snapshotter.service
5
6[Service]
7Type=simple
8RemainAfterExit=yes
9ExecStart=/usr/bin/socat TCP-LISTEN:55555,fork,reuseaddr,bind=127.0.0.1 UNIX-CONNECT:/run/soci-snapshotter-grpc/metrics.sock
10Restart=on-failure
11RestartSec=3
A .path unit was used to delay startup until the socket file appeared:
1# soci-snapshotter-metrics.path
2[Unit]
3Description=Monitor remote snapshotter config file for changes and start soci-snapshotter-metrics service
4After=containerd.service
5
6[Path]
7PathExists=/run/soci-snapshotter-grpc/metrics.sock
8Unit=soci-snapshotter-metrics.service
9
10[Install]
11WantedBy=multi-user.target
This worked fine, but it depends on socat being present. With Bottlerocket v11.0.0, it's gone.
The replacement: systemd-socket-proxyd
systemd-socket-proxyd is a small proxy daemon that ships with systemd itself. It reads from a socket passed by systemd (via socket activation) and forwards traffic to a target address, which can be a TCP address or a UDS path.
The setup uses two units: a .socket unit that owns the listening TCP port, and a .service unit that runs systemd-socket-proxyd and forwards to the UDS.
1# proxy-soci-for-otel.socket
2[Socket]
3ListenStream=55555
4
5[Install]
6WantedBy=sockets.target
1# proxy-soci-for-otel.service
2[Unit]
3Requires=soci-snapshotter.service
4After=soci-snapshotter.service
5Requires=proxy-soci-for-otel.socket
6After=proxy-soci-for-otel.socket
7
8[Service]
9Type=notify
10ExecStart=/usr/lib/systemd/systemd-socket-proxyd /run/soci-snapshotter-grpc/metrics.sock
11PrivateTmp=yes
12PrivateNetwork=yes
systemd-socket-proxyd receives the socket from systemd and proxies each connection to /run/soci-snapshotter-grpc/metrics.sock. The Type=notify tells systemd to wait for the process to signal readiness before marking the service active.
How it compares to the socat approach
With socat, the service itself called bind() on the TCP port. That means it had to start before any client tried to connect, and a restart window left the port temporarily closed.
With socket activation, systemd holds the port open at all times. The proxy service can be stopped, restarted, or crash, and the port stays bound. Connections queue up in the kernel until the service comes back. This is a cleaner model for a sidecar proxy.
The .path unit is also no longer needed. The Requires=soci-snapshotter.service dependency in the service unit handles ordering. If soci-snapshotter hasn't started yet, systemd won't start the proxy service either.