Docker: support running without `NET_BIND_SERVICE`
Environment
- ejabberd version: 25.10
- Erlang version: unknown
- OS: Alpine Linux
- Installed from: container (
ghcr.io/processone/ejabberd:latest)
Configuration
docker-compose.yml:
version: 3.9
networks:
chat-network:
external: true
services:
ejabberd:
image: ghcr.io/processone/ejabberd:latest
container_name: ejabberd
environment:
- CTL_ON_START=stats registeredusers
networks: [ chat-network ]
volumes:
- ./ejabberd.yml:/opt/ejabberd/conf/ejabberd.yml:ro
- ./database:/opt/ejabberd/database
- ./logs:/opt/ejabberd/logs
- ./upload:/opt/ejabberd/upload
- ./erlang.cookie:/opt/ejabberd/.erlang.cookie
- ./modules:/opt/ejabberd/.ejabberd-modules
# == RELEVANT CONFIGS ==
security_opt:
no-new-privileges: true
cap_drop:
- ALL
# cap_add:
# - NET_BIND_SERVICE
For ejabberd.yml, all listen modules are configured to listen on 5222, 5223, 5269, 5270, 5280, 5380, and 5480. Nothing is listening on ports below 1024.
Errors
From podman logs -f ejabberd:
erlexec: Error 1 executing '/opt/ejabberd-25.10/erts-15.2.7.2/bin/beam.smp'.
Bug description
I'm selfhosting ejabberd using podman, and would like to harden the setup by removing all unnecessary capabilities. One of those ways is by using these flags that result in the compose file as shown above:
security_opt:
no-new-privileges: true
cap_drop:
- ALL
However, this errors out as shown above, and the container process was unable to start. I was able to trace it down to a lack of the NET_BIND_SERVICE capability, and after adding those lines to the setup, it works:
security_opt:
no-new-privileges: true
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
The NET_BIND_SERVICE allows running on privileged ports (ports <1024). However, as explained above, all my ports are in the 5200+ ranges, which means this capability is unneeded.
Therefore, I'd like to be able to run the docker/binary without this capability in place, which helps dropping unnecessary privileges. Thanks in advance.
It would be great if you update your issue description providing all the details required to reproduce the problem. Right now there's just a part of a YAML file that I don't know where to put.
Rewritten to match bug report template
Thanks, now that allows to reproduce the scenario. I attempted many different approaches, but didn't find any clue about where the problem is. I post here what I tried, in case it gives some idea.
Let's check it works perfectly by default:
podman run --rm -it ghcr.io/processone/ejabberd:25.10
...
2025-11-19 15:14:11.077 [info] ejabberd 25.10.0 is started in the node :ejabberd@localhost in 1.48s
The minimal steps required to produce the problem:
$ podman run --rm -it --cap-drop NET_BIND_SERVICE ghcr.io/processone/ejabberd:25.10
erlexec: Error 1 executing '/opt/ejabberd-25.10/erts-15.2.7.2/bin/beam.smp'.
Removing all listened ports in ejabberd.yml, it got down just to the ports used for erlang network distribution:
$ netstat -ntulp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:39269 0.0.0.0:* LISTEN 2/beam.smp
tcp 0 0 :::4369 :::* LISTEN 32/epmd
tcp 0 0 0.0.0.0:4369 0.0.0.0:* LISTEN 32/epmd
This can be reduced to just one port by setting ERL_DIST_PORT=5210 in ejabberdctl.cfg:
$ netstat -ntulp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:5210 0.0.0.0:* LISTEN 2/beam.smp
Funnily, setting INET_DIST_INTERFACE=127.0.0.1 in ejabberdctl.cfg produces two error lines:
ec2a2ed9fe58218c1bfbe3121b3c20d57e3658be79bcf7672474a1fe02a0153b
[main] | erlexec: Error 1 executing '/opt/ejabberd-25.10/erts-15.2.7.2/bin/beam.smp'.
[main] | erlexec: Error 1 executing '/opt/ejabberd-25.10/erts-15.2.7.2/bin/beam.smp'.
Looking at ejabberdctl script, this tries to start the erlang BEAM with this command line:
exec /opt/ejabberd-25.10/erts-15.2.7.2/bin/erl
-sname ejabberd@localhost
-boot ../releases/25.10.0/start_clean
-boot_var RELEASE_LIB ../lib
+K false
+P 250000
-erl_epmd_port 5210
-start_epmd false
-mnesia dir "/opt/ejabberd/database"
-s ejabberd
-noinput
None of those options imply any usage of any port.
ejabberd:25.10 container image uses Erlang/OTP 27.3. I tried an ejabberd image using erlang 28.1, and the problem remains.
I tried to reproduce the problem using just Erlang, but could not.
Erlang in desktop with capsh:
sudo capsh --drop=CAP_NET_BIND_SERVICE -- -c erl
Erlang/OTP 27 [erts-15.2.7] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [jit:ns]
Eshell V15.2.7 (press Ctrl+G to abort, type help(). for help)
1>
Erlang in container:
$ podman run --rm -it --cap-drop NET_BIND_SERVICE docker.io/library/erlang:28.1.0.0-alpine
Erlang/OTP 28 [erts-16.1] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [jit:ns]
Eshell V16.1 (press Ctrl+G to abort, type help(). for help)
1>
ejabberd start script with caps:
$ sudo capsh --drop=CAP_NET_BIND_SERVICE -- -c "./bin/ejabberdctl live"
$ sudo capsh --drop=CAP_NET_BIND_SERVICE -- -c "exec /home/badlop/git/ejabberd/_build/prod/rel/ejabberd/erts-16.1/bin/erl -sname ejabberd@localhost +K true +P 250000 -s ejabberd -noinput"
2025-11-19 15:56:24.615210+01:00 [info] Loading configuration from ejabberd.yml
...