Container Image
View Sourceejabberd Container Images
ejabberd is an open-source, robust, scalable and extensible realtime platform built using Erlang/OTP, that includes XMPP Server, MQTT Broker and SIP Service.
This page documents those container images (images comparison):
published in ghcr.io/processone/ejabberd, built using ejabberd repository, both for stable ejabberd releases and themaster
branch, in x64 and arm64 architectures.
published in docker.io/ejabberd/ecs, built using docker-ejabberd/ecs repository for ejabberd stable releases in x64 architectures.
For Microsoft Windows, see Docker Desktop for Windows 10, and Docker Toolbox for Windows 7.
For Kubernetes Helm, see help-ejabberd.
Start ejabberd
daemon
Start ejabberd in a new container:
docker run --name ejabberd -d -p 5222:5222 ghcr.io/processone/ejabberd
That runs the container as a daemon,
using ejabberd default configuration file and XMPP domain localhost
.
Restart the stopped ejabberd container:
docker restart ejabberd
Stop the running container:
docker stop ejabberd
Remove the ejabberd container:
docker rm ejabberd
with Erlang console
Start ejabberd with an interactive Erlang console attached using the live
command:
docker run --name ejabberd -it -p 5222:5222 ghcr.io/processone/ejabberd live
That uses the default configuration file and XMPP domain localhost
.
with your data
Pass a configuration file as a volume and share the local directory to store database:
mkdir conf && cp ejabberd.yml.example conf/ejabberd.yml
mkdir database && chown ejabberd database
docker run --name ejabberd -it \
-v $(pwd)/conf/ejabberd.yml:/opt/ejabberd/conf/ejabberd.yml \
-v $(pwd)/database:/opt/ejabberd/database \
-p 5222:5222 ghcr.io/processone/ejabberd live
Notice that ejabberd runs in the container with an account named ejabberd
with UID 9000 and group ejabberd
with GID 9000,
and the volumes you mount must grant proper rights to that account.
Next steps
Register admin account
:orange_circle:
If you set the REGISTER_ADMIN_PASSWORD
environment variable,
an account is automatically registered with that password,
and admin privileges are granted to it.
The account created depends on what variables you have set:
EJABBERD_MACRO_ADMIN=juliet@example.org
->juliet@example.org
EJABBERD_MACRO_HOST=example.org
->admin@example.org
- None of those variables are set ->
admin@localhost
The account registration is shown in the container log:
:> ejabberdctl register admin example.org somePassw0rd
User admin@example.org successfully registered
Alternatively, you can register the account manually yourself
and edit conf/ejabberd.yml
and add the ACL as explained in
ejabberd Docs: Administration Account.
The default ejabberd configuration has already granted admin privilege
to an account that would be called admin@localhost
,
so you just need to register it, for example:
docker exec -it ejabberd ejabberdctl register admin localhost passw0rd
Check ejabberd log
Check the content of the log files inside the container, even if you do not put it on a shared persistent drive:
docker exec -it ejabberd tail -f logs/ejabberd.log
Inspect container files
The container uses Alpine Linux. Start a shell inside the container:
docker exec -it ejabberd sh
Open debug console
Open an interactive debug Erlang console attached to a running ejabberd in a running container:
docker exec -it ejabberd ejabberdctl debug
CAPTCHA
ejabberd includes two example CAPTCHA scripts. If you want to use any of them, first install some additional required libraries:
docker exec --user root ejabberd apk add imagemagick ghostscript-fonts bash
Now update your ejabberd configuration file, for example:
docker exec -it ejabberd vi conf/ejabberd.yml
and add this option:
captcha_cmd: "$HOME/bin/captcha.sh"
Finally, reload the configuration file or restart the container:
docker exec ejabberd ejabberdctl reload_config
If the CAPTCHA image is not visible, there may be a problem generating it (the ejabberd log file may show some error message); or the image URL may not be correctly detected by ejabberd, in that case you can set the correct URL manually, for example:
captcha_url: https://localhost:5443/captcha
For more details about CAPTCHA options, please check the CAPTCHA documentation section.
Advanced
Ports
The container image exposes several ports (check also Docs: Firewall Settings):
5222
: The default port for XMPP clients.5269
: For XMPP federation. Only needed if you want to communicate with users on other servers.5280
: For admin interface (URL isadmin/
).1880
: For admin interface (URL is/
, useful for podman-desktop and docker-desktop) :orange_circle:5443
: With encryption, used for admin interface, API, CAPTCHA, OAuth, Websockets and XMPP BOSH.1883
: Used for MQTT4369-4399
: EPMD and Erlang connectivity, used forejabberdctl
and clustering5210
: Erlang connectivity whenERL_DIST_PORT
is set, alternative to EPMD :orange_circle:
Volumes
ejabberd produces two types of data: log files and database spool files (Mnesia). This is the kind of data you probably want to store on a persistent or local drive (at least the database).
The volumes you may want to map:
/opt/ejabberd/conf/
: Directory containing configuration and certificates/opt/ejabberd/database/
: Directory containing Mnesia database. You should back up or export the content of the directory to persistent storage (host storage, local storage, any storage plugin)/opt/ejabberd/logs/
: Directory containing log files/opt/ejabberd/upload/
: Directory containing uploaded files. This should also be backed up.
All these files are owned by an account named ejabberd
with group ejabberd
in the container.
Its corresponding UID:GID
is 9000:9000
.
If you prefer bind mounts instead of volumes, then
you need to map this to valid UID:GID
on your host to get read/write access on
mounted directories.
If using Docker, try:
mkdir database
sudo chown 9000:9000 database
If using Podman, try:
mkdir database
podman unshare chown 9000:9000 database
It's possible to install additional ejabberd modules using volumes, check this Docs tutorial.
Commands on start
The ejabberdctl script reads the CTL_ON_CREATE
environment variable
the first time the container is started,
and reads CTL_ON_START
every time the container is started.
Those variables can contain one ejabberdctl command,
or several commands separated with the blankspace and ;
characters.
If any of those commands returns a failure, the container starting gets aborted.
If there is a command with a result that can be ignored,
prefix that command with !
This example, registers an admin@localhost
account when the container is first created.
Everytime the container starts, it shows the list of registered accounts,
checks that the admin account exists and password is valid,
changes the password of an account if it exists (ignoring any failure),
and shows the ejabberd starts (check also the full example):
environment:
- CTL_ON_CREATE=register admin localhost asd
- CTL_ON_START=stats registeredusers ;
check_password admin localhost asd ;
! change_password bot123 localhost qqq ;
status
Macros in environment :high_brightness:
ejabberd reads EJABBERD_MACRO_*
environment variables
and uses them to define the corresponding
macros,
overwriting the corresponding macro definition if it was set in the configuration file.
This is supported since ejabberd 24.12.
For example, if you configure this in ejabberd.yml
:
acl:
admin:
user: ADMIN
now you can define the admin account JID using an environment variable:
environment:
- EJABBERD_MACRO_ADMIN=admin@localhost
Check the full example for other example.
ejabberdapi
When the container is running (and thus ejabberd), you can exec commands inside the container
using ejabberdctl
or any other of the available interfaces, see
Understanding ejabberd "commands"
Additionally, the container image includes the ejabberdapi
executable.
Please check the ejabberd-api homepage
for configuration and usage details.
For example, if you configure ejabberd like this:
listen:
-
port: 5282
module: ejabberd_http
request_handlers:
"/api": mod_http_api
acl:
loopback:
ip:
- 127.0.0.0/8
- ::1/128
- ::FFFF:127.0.0.1/128
api_permissions:
"admin access":
who:
access:
allow:
acl: loopback
what:
- "register"
Then you could register new accounts with this query:
docker exec -it ejabberd ejabberdapi register --endpoint=http://127.0.0.1:5282/ --jid=admin@localhost --password=passw0rd
Clustering
When setting several containers to form a cluster of ejabberd nodes, each one must have a different Erlang Node Name and the same Erlang Cookie.
For this you can either:
- edit
conf/ejabberdctl.cfg
and set variablesERLANG_NODE
andERLANG_COOKIE
- set the environment variables
ERLANG_NODE_ARG
andERLANG_COOKIE
Example to connect a local ejabberdctl
to a containerized ejabberd:
- When creating the container, export port 5210, and set
ERLANG_COOKIE
:docker run --name ejabberd -it \ -e ERLANG_COOKIE=`cat $HOME/.erlang.cookie` \ -p 5210:5210 -p 5222:5222 \ ghcr.io/processone/ejabberd
- Set
ERL_DIST_PORT=5210
inejabberdctl.cfg
of container and local ejabberd - Restart the container
- Now use
ejabberdctl
in your local ejabberd deployment
To connect using a local ejabberd
script:
ERL_DIST_PORT=5210 _build/dev/rel/ejabberd/bin/ejabberd ping
Example using environment variables (see full example docker-compose.yml):
environment:
- ERLANG_NODE_ARG=ejabberd@node7
- ERLANG_COOKIE=dummycookie123
Once you have the ejabberd nodes properly set and running,
you can tell the secondary nodes to join the master node using the
join_cluster
API call.
Example using environment variables (see the full
docker-compose.yml
clustering example):
environment:
- ERLANG_NODE_ARG=ejabberd@replica
- ERLANG_COOKIE=dummycookie123
- CTL_ON_CREATE=join_cluster ejabberd@main
Change Mnesia Node Name
To use the same Mnesia database in a container with a different hostname, it is necessary to change the old hostname stored in Mnesia.
This section is equivalent to the ejabberd Documentation Change Computer Hostname, but particularized to containers that use this ecs container image from ejabberd 23.01 or older.
Setup Old Container
Let's assume a container running ejabberd 23.01 (or older) from this ecs container image, with the database directory binded and one registered account. This can be produced with:
OLDCONTAINER=ejaold
NEWCONTAINER=ejanew
mkdir database
sudo chown 9000:9000 database
docker run -d --name $OLDCONTAINER -p 5222:5222 \
-v $(pwd)/database:/opt/ejabberd/database \
ghcr.io/processone/ejabberd:23.01
docker exec -it $OLDCONTAINER ejabberdctl started
docker exec -it $OLDCONTAINER ejabberdctl register user1 localhost somepass
docker exec -it $OLDCONTAINER ejabberdctl registered_users localhost
Methods to know the Erlang node name:
ls database/ | grep ejabberd@
docker exec -it $OLDCONTAINER ejabberdctl status
docker exec -it $OLDCONTAINER grep "started in the node" logs/ejabberd.log
Change Mnesia Node
First of all let's store the Erlang node names and paths in variables. In this example they would be:
OLDCONTAINER=ejaold
NEWCONTAINER=ejanew
OLDNODE=ejabberd@95145ddee27c
NEWNODE=ejabberd@localhost
OLDFILE=/opt/ejabberd/database/old.backup
NEWFILE=/opt/ejabberd/database/new.backup
Start your old container that can still read the Mnesia database correctly. If you have the Mnesia spool files, but don't have access to the old container anymore, go to Create Temporary Container and later come back here.
Generate a backup file and check it was created:
docker exec -it $OLDCONTAINER ejabberdctl backup $OLDFILE ls -l database/*.backup
Stop ejabberd:
docker stop $OLDCONTAINER
Create the new container. For example:
docker run \ --name $NEWCONTAINER \ -d \ -p 5222:5222 \ -v $(pwd)/database:/opt/ejabberd/database \ ghcr.io/processone/ejabberd:latest
Convert the backup file to new node name:
docker exec -it $NEWCONTAINER ejabberdctl mnesia_change_nodename $OLDNODE $NEWNODE $OLDFILE $NEWFILE
Install the backup file as a fallback:
docker exec -it $NEWCONTAINER ejabberdctl install_fallback $NEWFILE
Restart the container:
docker restart $NEWCONTAINER
Check that the information of the old database is available. In this example, it should show that the account
user1
is registered:docker exec -it $NEWCONTAINER ejabberdctl registered_users localhost
When the new container is working perfectly with the converted Mnesia database, you may want to remove the unneeded files: the old container, the old Mnesia spool files, and the backup files.
Create Temporary Container
In case the old container that used the Mnesia database is not available anymore, a temporary container can be created just to read the Mnesia database and make a backup of it, as explained in the previous section.
This method uses --hostname
command line argument for docker,
and ERLANG_NODE_ARG
environment variable for ejabberd.
Their values must be the hostname of your old container
and the Erlang node name of your old ejabberd node.
To know the Erlang node name please check
Setup Old Container.
Command line example:
OLDHOST=${OLDNODE#*@}
docker run \
-d \
--name $OLDCONTAINER \
--hostname $OLDHOST \
-p 5222:5222 \
-v $(pwd)/database:/opt/ejabberd/database \
-e ERLANG_NODE_ARG=$OLDNODE \
ghcr.io/processone/ejabberd:latest
Check the old database content is available:
docker exec -it $OLDCONTAINER ejabberdctl registered_users localhost
Now that you have ejabberd running with access to the Mnesia database, you can continue with step 2 of previous section Change Mnesia Node.
Build Container Image
The container image includes ejabberd as a standalone OTP release built using Elixir.
Build ejabberd
The ejabberd Erlang/OTP release is configured with:
mix.exs
: Customize ejabberd releasevars.config
: ejabberd compilation configuration optionsconfig/runtime.exs
: Customize ejabberd pathsejabberd.yml.template
: ejabberd default config file
Direct build
Build ejabberd Community Server container image from ejabberd master git repository:
docker buildx build \
-t personal/ejabberd \
-f .github/container/Dockerfile \
.
Podman build
To build the image using Podman, please notice:
EXPOSE 4369-4399
port range is not supported, remove that in Dockerfile- It mentions that
healthcheck
is not supported by the Open Container Initiative image format - to start with command
live
, you may want to add environment variableEJABBERD_BYPASS_WARNINGS=true
podman build \
-t ejabberd \
-f .github/container/Dockerfile \
.
podman run --name eja1 -d -p 5222:5222 localhost/ejabberd
podman exec eja1 ejabberdctl status
podman exec -it eja1 sh
podman stop eja1
podman run --name eja1 -it -e EJABBERD_BYPASS_WARNINGS=true -p 5222:5222 localhost/ejabberd live
Build ecs
The ejabberd Erlang/OTP release is configured with:
rel/config.exs
: Customize ejabberd releaserel/dev.exs
: ejabberd environment configuration for development releaserel/prod.exs
: ejabberd environment configuration for production releasevars.config
: ejabberd compilation configuration optionsconf/ejabberd.yml
: ejabberd default config file
Build ejabberd Community Server base image from ejabberd master on Github:
docker build -t personal/ejabberd .
Build ejabberd Community Server base image for a given ejabberd version:
./build.sh 18.03
Composer Examples
Minimal Example
This is the barely minimal file to get a usable ejabberd.
If using Docker, write this docker-compose.yml
file
and start it with docker-compose up
:
services:
main:
image: ghcr.io/processone/ejabberd
container_name: ejabberd
ports:
- "5222:5222"
- "5269:5269"
- "5280:5280"
- "5443:5443"
If using Podman, write this minimal.yml
file
and start it with podman kube play minimal.yml
:
apiVersion: v1
kind: Pod
metadata:
name: ejabberd
spec:
containers:
- name: ejabberd
image: ghcr.io/processone/ejabberd
ports:
- containerPort: 5222
hostPort: 5222
- containerPort: 5269
hostPort: 5269
- containerPort: 5280
hostPort: 5280
- containerPort: 5443
hostPort: 5443
Customized Example
This example shows the usage of several customizations: it uses a local configuration file, defines a configuration macro using an environment variable, stores the mnesia database in a local path, registers an account when it's created, and checks the number of registered accounts every time it's started.
Prepare an ejabberd configuration file:
mkdir conf && cp ejabberd.yml.example conf/ejabberd.yml
Create the database directory and allow the container access to it:
- Docker:
mkdir database && sudo chown 9000:9000 database
- Podman:
mkdir database && podman unshare chown 9000:9000 database
If using Docker, write this docker-compose.yml
file
and start it with docker-compose up
:
version: '3.7'
services:
main:
image: ghcr.io/processone/ejabberd
container_name: ejabberd
environment:
- EJABBERD_MACRO_HOST=example.com
- EJABBERD_MACRO_ADMIN=admin@example.com
- REGISTER_ADMIN_PASSWORD=somePassw0rd
- CTL_ON_START=registered_users example.com ;
status
ports:
- "5222:5222"
- "5269:5269"
- "5280:5280"
- "5443:5443"
volumes:
- ./conf/ejabberd.yml:/opt/ejabberd/conf/ejabberd.yml:ro
- ./database:/opt/ejabberd/database
If using Podman, write this custom.yml
file
and start it with podman kube play custom.yml
:
apiVersion: v1
kind: Pod
metadata:
name: ejabberd
spec:
containers:
- name: ejabberd
image: ghcr.io/processone/ejabberd
env:
- name: EJABBERD_MACRO_HOST
value: example.com
- name: EJABBERD_MACRO_ADMIN
value: admin@example.com
- name: REGISTER_ADMIN_PASSWORD
value: somePassw0rd
- name: CTL_ON_START
value: registered_users example.com ;
status
ports:
- containerPort: 5222
hostPort: 5222
- containerPort: 5269
hostPort: 5269
- containerPort: 5280
hostPort: 5280
- containerPort: 5443
hostPort: 5443
volumeMounts:
- mountPath: /opt/ejabberd/conf/ejabberd.yml
name: config
readOnly: true
- mountPath: /opt/ejabberd/database
name: db
volumes:
- name: config
hostPath:
path: ./conf/ejabberd.yml
type: File
- name: db
hostPath:
path: ./database
type: DirectoryOrCreate
Clustering Example
In this example, the main container is created first. Once it is fully started and healthy, a second container is created, and once ejabberd is started in it, it joins the first one.
An account is registered in the first node when created (and we ignore errors that can happen when doing that - for example when account already exists), and it should exist in the second node after join.
Notice that in this example the main container does not have access to the exterior; the replica exports the ports and can be accessed.
If using Docker, write this docker-compose.yml
file
and start it with docker-compose up
:
version: '3.7'
services:
main:
image: ghcr.io/processone/ejabberd
container_name: main
environment:
- ERLANG_NODE_ARG=ejabberd@main
- ERLANG_COOKIE=dummycookie123
- CTL_ON_CREATE=! register admin localhost asd
healthcheck:
test: netstat -nl | grep -q 5222
start_period: 5s
interval: 5s
timeout: 5s
retries: 120
replica:
image: ghcr.io/processone/ejabberd
container_name: replica
depends_on:
main:
condition: service_healthy
environment:
- ERLANG_NODE_ARG=ejabberd@replica
- ERLANG_COOKIE=dummycookie123
- CTL_ON_CREATE=join_cluster ejabberd@main
- CTL_ON_START=registered_users localhost ;
status
ports:
- "5222:5222"
- "5269:5269"
- "5280:5280"
- "5443:5443"
If using Podman, write this cluster.yml
file
and start it with podman kube play cluster.yml
:
apiVersion: v1
kind: Pod
metadata:
name: cluster
spec:
containers:
- name: first
image: ghcr.io/processone/ejabberd
env:
- name: ERLANG_NODE_ARG
value: main@cluster
- name: ERLANG_COOKIE
value: dummycookie123
- name: CTL_ON_CREATE
value: register admin localhost asd
- name: CTL_ON_START
value: stats registeredusers ;
status
- name: EJABBERD_MACRO_PORT_C2S
value: 6222
- name: EJABBERD_MACRO_PORT_C2S_TLS
value: 6223
- name: EJABBERD_MACRO_PORT_S2S
value: 6269
- name: EJABBERD_MACRO_PORT_HTTP_TLS
value: 6443
- name: EJABBERD_MACRO_PORT_HTTP
value: 6280
- name: EJABBERD_MACRO_PORT_MQTT
value: 6883
- name: EJABBERD_MACRO_PORT_PROXY65
value: 6777
volumeMounts:
- mountPath: /opt/ejabberd/conf/ejabberd.yml
name: config
readOnly: true
- name: second
image: ghcr.io/processone/ejabberd
env:
- name: ERLANG_NODE_ARG
value: replica@cluster
- name: ERLANG_COOKIE
value: dummycookie123
- name: CTL_ON_CREATE
value: join_cluster main@cluster ;
started ;
list_cluster
- name: CTL_ON_START
value: stats registeredusers ;
check_password admin localhost asd ;
status
ports:
- containerPort: 5222
hostPort: 5222
- containerPort: 5280
hostPort: 5280
volumeMounts:
- mountPath: /opt/ejabberd/conf/ejabberd.yml
name: config
readOnly: true
volumes:
- name: config
hostPath:
path: ./conf/ejabberd.yml
type: File
Images Comparison
Let's summarize the differences between both container images. Legend:
- :sparkle: is the recommended alternative
- :orange_circle: added in the latest release (ejabberd 25.03)
- :high_brightness: added in the previous release (ejabberd 24.12)
- :low_brightness: added in the pre-previous release (ejabberd 24.10)
Source code | ejabberd/.github/container | docker-ejabberd/ecs |
Generated by | container.yml | tests.yml |
Built for | stable releases <br /> master branch | stable releases <br /> master branch zip |
Architectures | linux/amd64 <br /> linux/arm64 | linux/amd64 |
Software | Erlang/OTP 27.3.2-alpine <br /> Elixir 1.18.3 | Alpine 3.19 <br /> Erlang/OTP 26.2 <br /> Elixir 1.15.7 |
Published in | ghcr.io/processone/ejabberd | docker.io/ejabberd/ecs <br /> ghcr.io/processone/ecs |
| :blacksquare_button: Additional content |
| ejabberd-contrib | included | not included |
| ejabberdapi | included :orange_circle: | included |
| :black_square_button: Ports |
| 1880 for WebAdmin | yes :orange_circle: | yes :orange_circle: |
| 5210 for ERL_DIST_PORT
| supported | supported :orange_circle: |
| :black_square_button: Paths |
| $HOME
| /opt/ejabberd/
| /home/ejabberd/
|
| User data | $HOME
:sparkle: <br /> /home/ejabberd/
:orange_circle: | $HOME
<br /> /opt/ejabberd/
:sparkle: :low_brightness: |
| ejabberdctl
| ejabberdctl
:sparkle: <br /> bin/ejabberdctl
:orange_circle: | bin/ejabberdctl
<br /> ejabberdctl
:sparkle: :low_brightness: |
| captcha.sh
| $HOME/bin/captcha.sh
:orange_circle: | $HOME/bin/captcha.sh
:orange_circle: |
| *.sql
files | $HOME/sql/*.sql
:sparkle: :orange_circle: <br /> $HOME/database/*.sql
:orange_circle: | $HOME/database/*.sql
<br /> $HOME/sql/*.sql
:sparkle: :orange_circle: |
| Mnesia spool files | $HOME/database/
:sparkle: <br /> $HOME/database/NODENAME/
:orange_circle: | $HOME/database/NODENAME/
<br /> $HOME/database/
:sparkle: :orange_circle: |
| :black_square_button: Variables |
| [`EJABBERD_MACRO*](#macros-in-environment) | supported :high_brightness: | supported :high_brightness: | | Macros used in
ejabberd.yml| yes :orange_circle: | yes :orange_circle: | | [
EJABBERD_MACRO_ADMIN](#register-admin-account) | Grant admin rights :orange_circle: <br /> (default
admin@localhost) <br /> | Hardcoded
admin@localhost| | [
REGISTER_ADMIN_PASSWORD](#register-admin-account) | Register admin account :orange_circle: | unsupported | |
CTL_OVER_HTTP` | enabled :orange_circle: | unsupported |