This is a Docker container for running Xeoma, surveillance software developed by Felena Soft. It supports a wide range of security cameras, has low CPU overhead, and a very easy-to-use interface. The container is just for the server, and does not have a user interface. Run the client on any computer or mobile device, connecting to the server on port 8090. You can also configure Xeoma's cameras to be shown in the web UI, which is accessible on port 10090.
The container uses LinuxServer.io's Ubuntu base with s6 supervision. This is an independently maintained Xeoma image; application and container support remain with FelenaSoft and this repository, respectively.
This docker image is available on Docker Hub.
You can try out Xeoma using the trial version of the software, then purchase it when you are ready. Note the limitations of the trial version however -- settings aren't saved, and archived videos get deleted after 1 hour. Avoid the free version, as it cannot connect to your container. Make sure you read the EULA as you are effectively agreeing to it by running this docker.
You can use environment variables or a configuration file to configure this container. For passwords, use a password file, which is more secure than putting the password in an environment variable.
docker run -d --name=Xeoma -p 8090:8090 -p 10090:10090 -v /local/path/to/config:/config -v /local/path/to/archive:/archive -e PUID=1000 -e PGID=1000 -e UMASK=022 -e VERSION='latest' -e PASSWORD='<password>' coppit/xeoma
To generate configuration and password files instead, run:
docker run -d --name=Xeoma -p 8090:8090 -p 10090:10090 -v /local/path/to/config:/config -v /local/path/to/archive:/archive coppit/xeoma
On first run, the container creates xeoma.conf and an empty xeoma_password in the config directory, then exits. Put
only your password in xeoma_password with no quotes, escaping, comments, assignments, etc. Trailing newline characters
are removed. All other characters are read literally. The password file is created with owner-only permissions and an
existing file is never overwritten. If Xeoma's password-setting command fails, startup stops and the container logs a
clear error with its exit status. Correct the password and restart. Raw Xeoma diagnostics are withheld because they can
reveal password fragments.
The generated xeoma.conf documents the default password path without defining a password variable. Change VERSION or
MAC_ADDRESS there if needed; otherwise leave it unchanged. Restart the container after filling in the password file.
Existing configurations containing PASSWORD continue to work.
The archive folder holds the saved video recordings.
To access your xeoma server, simply download the same version from the Xeoma website and set it up to connect to a remote server using the IP address of the docker host and the password you selected.
NOTE: If you're opening port 10090 for the web server, you need to actually add the web server to at least one camera. Otherwise you'll get a "404 Not Found" message when you access the webpage with a browser.
See the notes below for special networking considerations depending on your cameras, and for licensing issues.
View logs using:
docker logs xeoma
Xeoma runs as LinuxServer's abc user, mapped to the numeric PUID and PGID you provide. Set these to the host user
and group that should own configuration and recordings. Use id -u and id -g on the Docker host to find those values.
Both default to 911. Set these options as container environment variables, not in xeoma.conf; leave Docker's
--user option unset so initialization can prepare storage and then drop privileges for Xeoma.
UMASK controls permissions on newly created files. It defaults to 022; typical settings are:
| UMASK | Files created with mode 666 | Directories created with mode 777 |
|---|---|---|
022 |
Owner writes; everyone reads | Owner writes; everyone reads/traverses |
002 |
Owner/group write; everyone reads | Owner/group write; everyone reads/traverses |
007 |
Owner/group read and write | Owner/group access only |
077 |
Owner access only | Owner access only |
The application may request more restrictive permissions or explicitly change them. UMASK does not add permissions or
change existing files. The old hourly recursive permission script has been removed so it does not override the
chosen mask. For example, use PUID=1000, PGID=100, and UMASK=007 to share newly created recordings with group 100.
When upgrading from the older Phusion-based image, choose PUID/PGID before starting. The first successful ownership
migration recursively changes ownership of /config, /archive, and /archive-cache, including existing files. This
can be a slow operation, so the UID/GID and a checksum record the startup history in /config/.xeoma-ownership.
Subsequent starts will skip this recursive step unless PUID/PGID changes or the history has changed since the previous
start. (Older images append to macs.txt without updating the marker, so downgrading, running an older image, and
re-upgrading triggers migration again.) Existing permission modes are preserved.
If you replace a storage mount, restore files with different owners, or want to repeat the migration, delete
/config/.xeoma-ownership and restart. The marker does not detect externally added files. Keep the same volume
mappings and networking/MAC settings to preserve configuration and licensing identity.
Recommended: mount your password file read-only at /config/xeoma_password. The container reads this fixed path
automatically. The file should only contain the password. Existing passwords from the environment or config remain
supported; a valid password file takes precedence over them.
You can use either or both of these mounts:
--mount type=bind,src=/local/path/to/config,dst=/config
--mount type=bind,src=/local/path/to/secrets/xeoma_password,dst=/config/xeoma_password,readonlyWith only the directory mount, the password comes from /local/path/to/config/xeoma_password. With both mounts, the
separate password file appears at /config/xeoma_password, hiding any file already there for the container's lifetime.
The password file mount also works without a host directory mounted at /config; this image then uses an anonymous
config volume. Map /config explicitly if you want predictable configuration persistence when recreating the container.
Settings from xeoma.conf are loaded first, then environment variables override matching names. Missing settings use
their defaults. After merging, a valid /config/xeoma_password overrides PASSWORD. An unreadable or empty file logs a
warning and falls back to the merged PASSWORD; an absent file preserves legacy behavior.
A password in a properly protected xeoma.conf remains supported without an environment-password warning and can be
about as secure as a separately protected password file. Both store plaintext credentials; file-based password loading
does not itself encrypt them. Restrict access to either file and its backups, and omit credentials when sharing settings.
A separate secret file makes it easier to mount the credential read-only and keep it outside /config and routine
configuration backups. Its benefit is separation and access control, rather than encryption.
For Docker Compose:
services:
xeoma:
image: coppit/xeoma
ports:
- "8090:8090"
- "10090:10090"
environment:
PUID: "1000"
PGID: "1000"
UMASK: "007"
VERSION: "latest"
volumes:
- ./config:/config
- ./archive:/archive
secrets:
- source: xeoma_password
target: /config/xeoma_password
secrets:
xeoma_password:
file: ./secrets/xeoma_passwordCreate ./secrets/xeoma_password containing your password and restrict access to that host file. Compose mounts it at
/config/xeoma_password. With docker run, use the read-only bind mount shown above. The file is read on each container start; restart after changing it.
The container does not print the password, though Xeoma still receives it through its password-setting command.
You can optionally use a folder for temporary storage of recording files until they are fully recorded, then moved to the archive. Using this parameter significantly reduces disk fragmentation. This can be useful for a large number of cameras, or when using an SSD for the main archive.
To use this feature, simply add an additional option when running the container: -v /local/path/to/archive-cache:/archive-cache.
The VERSION environment variable can be used to select the version of Xeoma to use. Values can be "latest",
"latest_beta", a version string like "17.5.5", or a URL that starts with "http://", "https://" or "ftp://". The default
value is "latest". The change history for Xeoma is here.
During startup, the desired version of Xeoma is downloaded as needed into the "downloads" subdirectory of the config
directory. Any files in that directory matching the pattern xeoma_*.tgz will be deleted. It is then installed
automatically.
Warning: By default, Xeoma will automatically detect new versions on startup and update itself. You should disable this feature in the user interface, and instead just rely on the container's version handling. If you're using a specific version of the software, this will prevent Xeoma from auto-updating it if the container restarts. If you're using the "latest" version, the container will already auto-update (even without a restart).
At startup, the container registers an hourly update job in root's crontab only when the resolved VERSION is latest
or latest_beta. LinuxServer's built-in cron service runs the job at 17 minutes past each hour; output goes to the
container logs. Pinned versions and custom download URLs have no hourly update job. Restarting after changing the
configured version removes any old registration and applies the new setting. The updater also checks the version when
invoked manually. If the saved version setting is missing or unreadable, the updater logs an error and skips the update
rather than defaulting to latest.
How licensing works is a bit unclear. As of version 16.12.26, the Lite version prohibits running inside virtual machines. Whether (and how!) this applies to docker containers is unclear. Your container may also need continuous internet access to validate the license.
When you register your software, the license will be stored in your config directory. So it will be carried across container updates, along with any configuration changes you made in the app. But if you ever delete the config directory, you might have to contact Felena soft for another registration key.
Be careful about choosing your networking settings before installing your license. If you have registered the software with host or bridged networking, then if you change to the other type of networking, you will see an error message. You should still be able to switch back.
However, if you have any issues, the container will append some information about the MAC address to the file macs.txt
each time it starts. If you have trouble getting the license to work, try using the --mac-address flag to the run
command to force your new container to have the same MAC address as your old one. This will only work if you are using
bridged networking.
Alternatively, or if you are running in a Kubernetes and cannot set your mac address, you can set the MAC_ADDRESS
variable, either in the container environment or in the xeoma.conf file. The container will set its own MAC address at
startup. Note that this may require the addition of the --cap-add=NET_ADMIN flag.
Finally, if all else fails, use the felenasoft website for help.
Depending on how your security camera works, you might need to enable host networking by adding --net=host to your run
command. If you are using IP cameras, you can run this container in bridged networking mode, which is more secure.
However, you will need to manually enter the URL for the camera, because the camera search feature probably won't work.
You can consult this website for information about rtsp:// URLs for
accessing the camera's low and high quality video streams.
If you find any bugs with the software that are related to the docker container, let me know and I'll investigate. If you find bugs that are related to the actual software or cameras, etc then contact FelenaSoft.
The default test suite requires Python 3, Bash at /bin/bash, tar, and standard Unix utilities on macOS or Linux. It
uses Python's built-in unittest module; no additional Python packages are needed.
From the repository root, run the default suite:
python3 -B -m unittest discover -s tests -vThe real Docker smoke test is skipped unless explicitly enabled. The default suite uses temporary directories, mock
commands, and an HTTP server bound to 127.0.0.1 on an automatically selected port. Your environment must allow
loopback connections. No external internet access or Docker server is needed for these tests.
| Test file | Coverage |
|---|---|
test_config.py |
Settings, secret files, defaults, environment precedence, stale settings, and first-run setup |
test_build.py |
Development/publish command selection, help, invalid arguments, and Docker command failures |
test_installer.py |
Stable/beta/pinned/custom versions, downloads, fallback, caching, extraction, and failures |
test_init.py |
Conditional hourly-job registration, version changes, and failed startup cleanup |
test_updates.py |
Installing updates, restart requests, unchanged/pinned versions, and failed downloads |
test_configure.py |
Password arguments, MAC handling, storage links, repeated setup, and command failures |
test_docker.py |
Real image, secrets, UID/GID, umask, cron, service restart, and storage persistence |
For example, run just the installer tests with:
python3 -B -m unittest discover -s tests -p test_installer.py -vThe installer tests serve fixture version XML and small archives from the local HTTP endpoint. They execute the real installer with redirected paths and URLs, including real archive extraction and installation fingerprints. The update tests call that installer and record service restart requests instead of killing processes. The Xeoma configuration tests create real temporary storage links and record calls to Xeoma and network commands. They do not change your network interfaces, passwords, existing containers, or stored camera settings.
To also verify the actual image and proprietary Xeoma executable, enable the Docker smoke test:
XEOMA_DOCKER_TESTS=1 python3 -B -m unittest discover -s tests -p test_docker.py -vThis requires Docker with Buildx, a reachable Linux Docker server capable of running linux/amd64 images, and internet
access for the base image, packages, and Xeoma download. It uses your current Docker context, including a remote server.
The test builds a unique coppit/xeoma-test:suite-... image and starts a disposable container with anonymous volumes.
It publishes no ports and uses no existing host directories or volumes. A temporary test password file is copied in.
It checks that Xeoma listens on port 8090 inside the container, runs with the requested UID/GID and umask, and survives
service and container restarts. It also checks secret-file configuration, legacy password fallback, read-only password mounts with and without a parent directory mount, and cron
availability. It removes its containers, test volumes, and image
afterward; Docker's build cache remains. It never pushes an image.
By default, this downloads the latest stable Xeoma release. To select a specific version:
XEOMA_DOCKER_TESTS=1 XEOMA_TEST_VERSION=25.8.22 python3 -B -m unittest discover -s tests -p test_docker.py -vAllow several minutes for a fresh build and download. External service failures can cause this optional test to fail. This is a startup smoke test, not a camera recording, licensing, or client authentication test.
New test files should be named tests/test_*.py and use unittest.TestCase. Keep new lines within 120 characters.
This docker container was initially based on the jedimonkey/xeoma container.
Thanks to https://github.com/skylord123 on github for the excellent suggestions about how to handle versioning.