Running in Docker
docker run --network host \
-v /srv/lyno:/etc/lyno:ro \
ghcr.io/telqo/lyno:main \
-config /etc/lyno/configs/lyno.yaml -headless
Mount a directory holding configs/ and sounds/ — the same layout as the
repository — so the relative paths inside the YAML keep resolving.
The image is built FROM scratch-sized on distroless: a static binary, about
19 MB, no shell and no package manager.
Compose
services:
lyno:
image: ghcr.io/telqo/lyno:main
network_mode: host
restart: unless-stopped
volumes:
- ./:/etc/lyno:ro
command:
- -config
- /etc/lyno/configs/lyno.yaml
- -headless
Two things decide whether this works
1. Use host networking
SIP carries IP addresses inside its payload and RTP uses a wide UDP range. Docker's bridge networking rewrites addresses and cannot practically publish ten thousand UDP ports. The symptoms are the classic VoIP ones:
- calls that ring but never connect;
- one-way audio;
- audio that works locally and fails from outside.
network_mode: host avoids all of it, and the firewall on the host does the
filtering.
If you genuinely have no choice, you must:
- set
sip.external_hostandmedia.external_ipto the address the outside world sees; - narrow
media.rtp_port_min/rtp_port_maxto a range small enough to publish, and publish exactly that range plus the SIP port.
sip:
bind_host: 0.0.0.0
bind_port: 5060
external_host: 203.0.113.10
media:
external_ip: 203.0.113.10
rtp_port_min: 16384
rtp_port_max: 16584 # 100 concurrent streams, publishable
services:
lyno:
image: ghcr.io/telqo/lyno:main
ports:
- "5060:5060/udp"
- "16384-16584:16384-16584/udp"
volumes:
- ./:/etc/lyno:ro
Each concurrent call leg needs a couple of ports, so size the range for your peak concurrency and then some. See Running behind NAT.
2. Mind the file ownership
The image runs as uid 65532, and configuration files are mode 0600 because
they contain SIP passwords. A container that cannot read them fails at startup
with a permission error.
Either chown the tree:
sudo chown -R 65532:65532 /srv/lyno/configs
or run the container as the user that already owns them:
docker run --user "$(id -u):$(id -g)" ...
services:
lyno:
user: "1000:1000"
Tags
| Tag | Meaning |
|---|---|
main | The latest commit on the default branch. |
vX.Y.Z | A released version. |
Images are multi-arch and are only published from main and from tags; pull
requests build without publishing.
Pin a version tag in production. main moves.
Validating inside the container
docker run --rm -v /srv/lyno:/etc/lyno:ro ghcr.io/telqo/lyno:main \
-config /etc/lyno/configs/lyno.yaml -validate
Worth doing before restarting a running instance: the same binary, the same paths, the same file permissions.
Logs
Always run with -headless in a container. The dashboard needs an interactive
terminal, and while Lyno detects a non-terminal stdout and falls back on its
own, being explicit keeps the intent visible in the compose file.
Upgrading
- Pull the new image.
- Run
-validateagainst your configuration with the new image — a schema change surfaces here rather than at restart. - Recreate the container.
Calls in progress are dropped on restart; there is no graceful drain.