Skip to content

Architecture

Surfly uses a pluggable architecture built on systemd quadlets and podman containers. This design provides flexibility for customization while maintaining deployment consistency and reliability.

Surfly services are deployed as systemd quadlets - a modern approach that combines the benefits of containerization with systemd service management. Each service is defined by a .container file that specifies:

  • Container image and version
  • Volume mounts and networking
  • Environment configuration
  • Service dependencies and restart policies

Here’s the quadlet configuration for the HAProxy service:

[Unit]
Description=Reverse proxy server for Surfly
PartOf=ss-core.target
[Install]
WantedBy=multi-user.target default.target
[Service]
SyslogIdentifier=ss-haproxy
Restart=on-failure
SuccessExitStatus=143
TimeoutStopSec=70
[Container]
ContainerName=ss-haproxy
LogDriver=journald
Network=host
RunInit=true
Image=registry.surfly.com/surfly/ss-haproxy-production:c24017551b64b07021e2fa7c0055c14399af7f61
Volume=%h/surfly:/surfly
Volume=%h/surfly/certs:/certs
EnvironmentFile=%h/surfly/config.env
EnvironmentFile=%h/surfly/license.env
Exec=haproxy -Ws -f /app/haproxy.cfg -p /run/haproxy.pid

Systemd automatically converts quadlet .container files into standard systemd service files using the podman-user-generator. You can view the generated service file to understand how your quadlet configuration translates to systemd directives:

Terminal window
systemctl --user cat ss-haproxy

This shows the generated service file with additional systemd-specific configurations:

/run/user/1000/systemd/generator/ss-haproxy.service
# Automatically generated by /usr/lib/systemd/user-generators/podman-user-generator
[Unit]
Wants=podman-user-wait-network-online.service
After=podman-user-wait-network-online.service
Description=Reverse proxy server for Surfly
PartOf=ss-core.target
SourcePath=/home/client/.config/containers/systemd/ss-haproxy.container
RequiresMountsFor=%t/containers
RequiresMountsFor=/etc/pki/ca-trust/extracted
[Install]
WantedBy=multi-user.target default.target
[Service]
SyslogIdentifier=ss-haproxy
Restart=on-failure
SuccessExitStatus=143
TimeoutStopSec=70
Environment=PODMAN_SYSTEMD_UNIT=%n
KillMode=mixed
ExecStop=/usr/bin/podman rm -v -f -i --cidfile=%t/%N.cid
ExecStopPost=-/usr/bin/podman rm -v -f -i --cidfile=%t/%N.cid
Delegate=yes
Type=notify
NotifyAccess=all
ExecStart=/usr/bin/podman run --name ss-haproxy --cidfile=%t/%N.cid --replace --rm --log-driver journald --cgroups=split --init --network host --sdnotify=conmon -d -v %h/surfly/certs:/certs -v /etc/pki/ca-trust/extracted:/etc/pki/ca-trust/extracted --env NGINX_DNS_RESOLVERS=1.1.1.1 --env SENTRY_ENVIRONMENT=stag-dev-cobro-eufi --env-file %h/surfly/config.env --env-file %h/surfly/license.env registry.surfly.com/surfly/ss-haproxy-production:c24017551b64b07021e2fa7c0055c14399af7f61 haproxy -Ws -f /app/haproxy.cfg -p /run/haproxy.pid
[X-Container]
ContainerName=ss-haproxy
LogDriver=journald
Network=host
RunInit=true
Image=registry.surfly.com/surfly/ss-haproxy-production:c24017551b64b07021e2fa7c0055c14399af7f61
Volume=%h/surfly:/surfly
Volume=%h/surfly/certs:/certs
EnvironmentFile=%h/surfly/config.env
EnvironmentFile=%h/surfly/license.env
Exec=haproxy -Ws -f /app/haproxy.cfg -p /run/haproxy.pid
Environment="NGINX_DNS_RESOLVERS=1.1.1.1"
Environment="SENTRY_ENVIRONMENT=stag-dev-cobro-eufi"

This generated service file represents the final configuration after applying all overrides and merging all environment sources.

You can also generate all services in dry-run mode by running:

Terminal window
/usr/lib/systemd/system-generators/podman-system-generator --user --dryrun

Create systemd service overrides to customize any aspect of a service:

Terminal window
# Create override directory
mkdir -p ~/.config/systemd/user/ss-haproxy.service.d/
# Create override file
cat > ~/.config/systemd/user/ss-haproxy.service.d/custom.conf << EOF
[Container]
# Add custom volume mount
Volume=%h/custom-config:/custom-config:ro
# Add environment variable
Environment=CUSTOM_SETTING=value
EOF
# Reload and restart
systemctl --user daemon-reload
systemctl --user restart ss-haproxy

For more detailed information about available override options, see the podman-systemd.unit documentation.

Surfly supports systemd quadlets as the official deployment method. However, since all services are containerized microservices, it is possible to migrate to other deployment methods by converting the quadlet definitions to other formats.

Example converting quadlet definitions to Docker Compose format:

services:
haproxy:
image: registry.surfly.com/surfly/ss-haproxy-production:latest
network_mode: host
volumes:
- ~/surfly:/surfly
- ~/surfly/certs:/certs
env_file:
- ~/surfly/config.env
- ~/surfly/license.env
command: haproxy -Ws -f /app/haproxy.cfg -p /run/haproxy.pid
restart: on-failure

Example converting quadlet definitions to Kubernetes deployments with appropriate manifests:

apiVersion: apps/v1
kind: Deployment
metadata:
name: ss-haproxy
spec:
replicas: 1
selector:
matchLabels:
app: ss-haproxy
template:
spec:
containers:
- name: haproxy
image: registry.surfly.com/surfly/ss-haproxy-production:latest
command: ["haproxy", "-Ws", "-f", "/app/haproxy.cfg"]
volumeMounts:
- name: surfly-config
mountPath: /surfly
envFrom:
- configMapRef:
name: surfly-config