This setup packs a ejabberd XMPP Server with a Biboumi IRC Gateway in a docker-compose.yml
file.
The whole service lives in a single folder to ease backup and migration. All persistent data is stored in ./data/
. All configurations are made in ejabberd.yml
and in a .env
file. For consistent backups, a cronjob one-liner, running in the ejabberd container, dumps daily the ejabberd DB into ./data/backup/
.
It is assumed that on the docker host we're already running
FROM scratch
. All parameters are set using environment variables defined in docker-compose.yml
. The server runs under a unprivileged user (uid 1000).Marked [x] ports will be opened on the Docker host, so they should not be used by another service on the same host.
5222:5222
Client 2 Server — bare minimum to speak with clients5223:5223
XMPP over TLS is actually deprecated, instead STARTTLS on port 5222 should be used. It is active for backward compatibility or could be moved to port 443 to ease usage behind firewalls.5269:5269
Server 2 Server — only needed when we want to speak with users from other servers5280:5280
Web admin (disabled), BOSH and WebSocket5443:5443
HTTP Upload113:8113
Identd so IRC servers can differentiate between users443!8080
Integrated webserver to serve static content, such as a javascript xmpp client. The port is exposed via the reverse proxy.5347:5347
Only used for internal unencrypted communication with the Biboumi (IRC gateway) component----:4560
XMLRPC — API, disabledBOSH (previously known as 'HTTP binding' or "http-bind") is a technology to use XMPP over HTTP. This allows XMPP applications to run in web pages, but also any other HTTP-only environment such as behind restrictive firewalls.
ejabber runs under user ejabberd
with uid/guid 9000
. All data is stored in /home/ejabberd/
within the container.
./data/conf/ejabberd.yml
— main configuration file.data/database/
— Erlang DB used to store all application data.data/backup/
— place to to store DB dumps.data/uploads/
— files transferred between users.data/cron/backup.sh
— job to daily dump the DB./opt/docker/proxy/data/certs/im.example.net/
— certificates and key material (to be changed as per the local docker host setup).data/www/
— static web content./data/biboumi/database/
— sqlite DB for Biboumi. The Biboumi configuration is defined via environment variables in docker-compose.yml
.To allow service discovery, following DNS records have to be created.
im 3600 IN A 1.2.3.4
*.im 3600 IN CNAME in
#_service._proto.name TTL class SRV priority weight port target
_xmpp-client._tcp.im.example.net. 3600 IN SRV 5 0 5222 im.example.net.
_xmpp-clients._tcp.im.example.net. 3600 IN SRV 10 0 5223 im.example.net.
_xmpp-server._tcp.im.example.net. 3600 IN SRV 5 0 5269 im.example.net.
To check the records: https://kingant.net/check_xmpp_dns/
Prepare data folders with proper permissions.
inst_dir=/opt/docker/ejabberd
mkdir -p $inst_dir/data/conf \
$inst_dir/data/database \
$inst_dir/data/backup \
$inst_dir/data/upload \
$inst_dir/data/cron \
$inst_dir/data/www \
&& chown -R 9000:9000 $inst_dir/data/
mkdir -p $inst_dir/data/biboumi/database
chown -R 1000:1000 $inst_dir/data/biboumi/
Obtain a vanilla ejabberd.yml
configuration file.
We'll briefly spawn a volatile container and copy the file to the host.
docker run -d --rm --name ejabberd ejabberd/ecs
docker cp ejabberd:/home/ejabberd/conf/ejabberd.yml $inst_dir/data/conf/
docker kill ejabberd
Configure the server / tune ejabberd.yml
hostname=im.example.com
config_file=$inst_dir/data/conf/ejabberd.yml
# Set the hostname in .env to be used in docker-compose.yml
echo "HOSTNAME=$hostname" > $inst_dir/.env
# Set hostname and disable localhost configuration as we have no certificate for it
sed -i \
-e "s/^hosts:/hosts:\\n - \"$hostname\"/" \
-e '/^ - "localhost"/s/^/# /' \
$config_file
## Disable web admin, api and oauth as not used and to reduce attack surface
sed -i \
-e '/web_admin: true/s/^/# /' \
-e '/\/api/s/^/# /' \
-e '/\/oauth/s/^/# /' \
$config_file
## Enable TLS for BOSH and WebSockets
sed -i \
-e '/\/bosh/a\ tls: true' \
$config_file
## Logs: Rotate at 1MB, don't keep old versions
# The server is started in foreground and therefore logs are collected by docker,
# so we actually don't need logs at all and can rotate rapidly without keeping old version
sed -i \
-e '/log_rotate_size/s/10485760/1048576/' \
-e '/log_rotate_count/s/1/0/' \
$config_file
## Point to letsencrypt certificates from jrcs/letsencrypt-nginx-proxy-companion,
## for details, see: https://github.com/JrCs/docker-letsencrypt-nginx-proxy-companion
sed -i \
-e '/^certfiles/a\ - \"/home/ejabberd/ssl/*.pem\"' \
-e '/server.pem/s/^/# /' \
$config_file
## Enforce TLS connection for c2s
sed -i \
-e '/ ## starttls_required: true/a\ starttls_required: true' \
$config_file
## Enable XEP-0313 Message Archive Management,
## for details, see: https://xmpp.org/extensions/xep-0313.html)
sed -i \
-e '/ ## mod_mam/a\ mod_mam: {}' \
$config_file
## Enable OMEMO (XEP-0384),
## for details, see: https://github.com/processone/ejabberd/issues/2425
sed -i \
-e '/OMEMO/,+3 s/^/#/' \
$config_file
## Enable Roster Versioning (XEP-0237)
sed -i \
-e '/^ mod_roster: {}/a\ mod_roster:\n versioning: true' \
-e '/^ mod_roster: {}/s/^/#/' \
$config_file
## Enable SOCKS5 Bytestreams Proxy (XEP-0065)
sed -i -e '/mod_proxy65/s/## //' $config_file
## Enable legacy XMPP via TLS (XEP-0368)
heredoc=`tempfile`
cat > $heredoc <<'EOF'
-
port: 5223
ip: "::"
module: ejabberd_c2s
tls: true
max_stanza_size: 65536
shaper: c2s_shaper
access: c2s
EOF
sed -i "/^listen:$/r $heredoc" $config_file
rm $heredoc
## Enable HTTP File Upload Listener(XEP-0363)
heredoc=`tempfile`
cat > $heredoc <<'EOF'
-
port: 5443
ip: "::"
module: ejabberd_http
request_handlers:
"": mod_http_upload
tls: true
EOF
sed -i "/^listen:$/r $heredoc" $config_file
rm $heredoc
## Enable HTTP File Upload Module
## with quota the keep files max 3 days and max size of 5MB per file
heredoc=`tempfile`
cat > $heredoc <<'EOF'
mod_http_upload:
put_url: "https://@HOST@:5443/upload"
thumbnail: false # otherwise needs ejabberd to be compiled with libgd support
max_size: 524288 # 5MB
mod_http_upload_quota:
max_days: 3
EOF
sed -i "/^modules:$/r $heredoc" $config_file
rm $heredoc
## Add component for the IRC gateway
heredoc=`tempfile`
cat > $heredoc <<EOF
-
port: 5347
ip: "::"
module: ejabberd_service
access: all
hosts:
"irc.$hostname":
password: "secret"
EOF
sed -i "/^listen:$/r $heredoc" $config_file
rm $heredoc
## Enable simple fileserver
heredoc=`tempfile`
cat > $heredoc <<'EOF'
-
port: 8080
ip: "::"
module: ejabberd_http
request_handlers:
"/": mod_http_fileserver
EOF
sed -i "/^listen:$/r $heredoc" $config_file
rm $heredocmkdir -p $inst_dir/data/biboumi/database
chown -R 1000:1000 $inst_dir/data/biboumi/
sed -i \
-e '/## mod_http_fileserver:/s/## //' \
-e '/mod_http_fileserver:/a\ default_content_type: "text/html"' \
-e '/mod_http_fileserver:/a\ docroot: "/var/www/"' \
-e '/mod_http_fileserver:/a\ directory_indices:\n - "index.html"' \
$config_file
## Disable legacy SSL protocols and ciphers
sed -i \
-e '/c2s_protocol_options:/ac2s_protocol_options:\n - "no_sslv2"\n - "no_sslv3"\n - "no_tlsv1"\n - "no_tlsv1_1"\n' \
\
-e '/s2s_protocol_options:/as2s_protocol_options:\n - "no_sslv2"\n - "no_sslv3"\n' \
\
-e '/## s2s_ciphers:/as2s_ciphers: "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS"\n' \
\
-e '/## c2s_ciphers:/ac2s_ciphers: "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256"\n' \
\
-e '/^ port: 5443/a\ protocol_options:\n - "no_sslv2"\n - "no_sslv3"\n - "no_tlsv1"\n - "no_tlsv1_1"\n ciphers: "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256"\n' \
\
-e '/^ port: 5280/a\ protocol_options:\n - "no_sslv2"\n - "no_sslv3"\n - "no_tlsv1"\n - "no_tlsv1_1"\n ciphers: "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256"\n' \
$config_file
Setup the cronjob for dumping daily the DB
cat >$inst_dir/data/cron/dumb_db.sh <<'EOF'
#!/bin/sh
/home/ejabberd/bin/ejabberdctl backup /home/ejabberd/backup/ejabberd.backup
EOF
chmod a+x $inst_dir/data/cron/dumb_db.sh
Start the container. During the first startup the databases will be created.
docker-compose up -d
Create users
ejabberd comes by default with no users. Privileges and ACLs for users are set in ejabberd.yml
.
In the default configuration file admin@localhost
is set as privileged user already.
To create new users, run from the docker host:
docker exec -it ejabberd /home/ejabberd/bin/ejabberdctl register <user> <domain> <password>
We'll need to create at least a user for the IRC gateway.
user=ircadmin
pass=`apg -q -n1 -m12`
docker exec -it ejabberd /home/ejabberd/bin/ejabberdctl register $user $hostname $pass
Further self-registration or LDAP authentication can be enabled in `ejabberd.yml`.
(Optional) Run the XMPP compliance tester
jar_file=ComplianceTester-0.2.3.jar
dl_url=https://gultsch.de/files/$jar_file
domain=$hostname
docker run --rm -it --name=xmpptest openjdk:alpine \
/bin/sh -c "wget $dl_url ; java -jar $jar_file $user@$domain $pass" \
| tee $domain.txt
(Optional) Setup a static website
data/www/
can be used to serve a static site,
e.g. for presenting compliance test results,
a web xmpp client or client setup instructions
cd $inst_dir/data/www
git clone --depth=1 https://github.com/conversejs/converse.js.git
mv converse.js webchat
cp webchat/fullscreen.html webchat/index.html
sed -i \
-e '/analytics/d' \
-e '/piwik/d' \
-e "/bosh_service_url/s/conversejs.org/$hostname:5280/" \
-e "/bosh_service_url/s/http-bind/bosh/" \
webchat/index.html
docker exec -it ejabberd sh
docker exec -it ejabberd /home/ejabberd/bin/ejabberdctl \
registered_users <host>
List all registered users in HOSTunregister <user> <host>
List all registered users in HOSTmodules_available
List the contributed modules available to installmodules_installed
List the contributed modules already installedmnesia
Get details about the databasereload_config
Reload ejabberd configuration file into memory (this will not start new servers)connected_users
list connected users with their resourcesbackup /home/ejabberd/backup/ejabberd.backup
Backup databaseinstall_fallback /home/ejabberd/backup/ejabberd.backup
restores the db and makes it active after the next restarthelp
lists available commandsfirefox https://www.sslshopper.com/ssl-checker.html#hostname=irc.example.net:6697
Docker's randomly generated hostnames causes ejabberd to calculate different unique node and DB name. To prevent a new DB beeing created with each container restart, use docker run --hostname <hostname>
or hostname:
and domainname:
in the docker-compose.yml
.
/opt/docker/proxy/data/certs/<hostname>
should be readable by ejabberd
user.