opsi Docker Compose files
Docker Compose files to spin up an instance of opsi open source device management system. This specifically launches a so-called opsi Config Server. In opsi lingo Config Server is the central hub that includes among other components an opsi Depot Server. See also opsi 4.3 English docs section "opsi Server" for reference.
How to run
Add a COMPOSE_ENV
file and save its location as a shell variable along with the location where this repo lives, here for example /opt/containers/opsi
plus all other variables. At env/fqdn_context.env.example you'll find an example environment file.
When everything's ready start opsi with Docker Compose, otherwise head down to Initial setup first.
Environment
Make sure the upstream github.com/opsi-org/opsi-docker repository is checked out locally. We're going with example dir /opt/git/github.com/opsi-org/opsi-docker/branch/main
. We're also assuming that this repo exists at /opt/containers/opsi
.
export UPSTREAM_REPO_DIR='/opt/git/github.com/opsi-org/opsi-docker/branch/main'
export UPSTREAM_COMPOSE_FILE="${UPSTREAM_REPO_DIR%/}"'/opsi-server/docker-compose.yml'
export UPSTREAM_ENV_FILE="${UPSTREAM_REPO_DIR%/}"'/opsi-server/opsi-server.env'
export CONTEXT='ux_vilnius'
export COMPOSE_PROJECT_NAME='opsi-'"${CONTEXT}"
export COMPOSE_ENV=<add accordingly>
export COMPOSE_OVERRIDE='/opt/containers/opsi/compose.override.yaml'
In opsi's Git repo check out newest commit:
git -C "${UPSTREAM_REPO_DIR:?}" reset --hard origin/main
git -C "${UPSTREAM_REPO_DIR:?}" checkout main
git -C "${UPSTREAM_REPO_DIR:?}" pull
Context
On your deployment machine create the necessary Docker context to connect to and control the Docker daemon on whatever target host you'll be using, for example:
docker context create fully.qualified.domain.name --docker 'host=ssh://root@fully.qualified.domain.name'
Pull
Pull images from Docker Hub verbatim.
docker compose \
--project-name "${COMPOSE_PROJECT_NAME}" \
--file "${UPSTREAM_COMPOSE_FILE}" \
--file "${COMPOSE_OVERRIDE}" \
--env-file "${UPSTREAM_ENV_FILE}" \
--env-file "${COMPOSE_ENV}" \
pull
Copy to target
Copy images to target Docker host, that is assuming you deploy to a machine that itself has no network route to reach Docker Hub or your private registry of choice. Copying in its simplest form involves a local docker save
and a remote docker load
. Consider the helper mini-project quico.space/Quico/copy-docker where copy-docker.sh allows the following workflow:
images="$(docker compose --project-name "${COMPOSE_PROJECT_NAME}" --file "${UPSTREAM_COMPOSE_FILE}" --file "${COMPOSE_OVERRIDE}" --env-file "${UPSTREAM_ENV_FILE}" --env-file "${COMPOSE_ENV}" config | grep -Pi -- 'image:' | awk '{print $2}' | sort | uniq)"
while IFS= read -u 10 -r image; do
copy-docker "${image}" fully.qualified.domain.name
done 10<<<"${images}"
This will for example copy over:
# docker image ls -a
REPOSITORY TAG IMAGE ID CREATED SIZE
grafana/grafana latest 0a7de979b313 2 weeks ago 723MB
redis/redis-stack-server latest 1ebedd176a23 7 weeks ago 513MB
uibmz/opsi-server 4.3 f07683f1828b 2 months ago 996MB
mariadb 10.7 895b6c8829c3 2 years ago 396MB
Start
docker \
--context 'fully.qualified.domain.name' \
compose \
--project-name "${COMPOSE_PROJECT_NAME}" \
--file "${UPSTREAM_COMPOSE_FILE}" \
--file "${COMPOSE_OVERRIDE}" \
--env-file "${UPSTREAM_ENV_FILE}" \
--env-file "${COMPOSE_ENV}" \
up --detach
Clean up
docker --context 'fully.qualified.domain.name' system prune -af
docker system prune -af
Initial setup
We're assuming you run Docker Compose workloads with ZFS-based bind mounts. ZFS management, creating a zpool and setting adequate properties for its datasets is out of scope of this document.
Datasets
Create ZFS datasets and set permissions as needed.
-
Parent dateset
export "$(grep -Pi -- '^CONTEXT=' "${COMPOSE_ENV}" | xargs --max-lines 1)" zfs create -o canmount=off zpool/data/opt zfs create -o mountpoint=/opt/docker-data zpool/data/opt/docker-data
-
Container-specific datasets
zfs create -p 'zpool/data/opt/docker-data/opsi-'"${CONTEXT}"'/grafana/data' zfs create -p 'zpool/data/opt/docker-data/opsi-'"${CONTEXT}"'/mysql/data' zfs create -p 'zpool/data/opt/docker-data/opsi-'"${CONTEXT}"'/opsi/data' zfs create -p 'zpool/data/opt/docker-data/opsi-'"${CONTEXT}"'/redis/data'
All four opsi components, that it Grafana, MySQL, opsi and Redis have one subdirectory for their
data
volume. None of them require aconfig
volume or other mounts. -
Create subdirs
mkdir -p '/opt/docker-data/opsi-'"${CONTEXT}"'/opsi/'{'.ssh','config','data','projects'}
-
Change ownership
chown -R 472:472 'zpool/data/opt/docker-data/opsi-'"${CONTEXT}"'/grafana/data'
Here you'll want to accommodate the Grafana container. Per its upstream Dockerfile retrieved from
Dockerfile
in the github.com/grafana/grafana repository in August 2025 all Grafana images that end up at Docker Hub are built with the directive:USER "$GF_UID"
Where
"$GF_UID"
is populated withARG GF_UID="472"
.
Additional files
With preparations out of the way opsi does not require any additional files present on your Docker host's filesystem, as bind mounts or via other means.
Head back up to How to run.
Development
Conventional commits
This project uses Conventional Commits for its commit messages.
Commit types
Commit types besides fix
and feat
are:
refactor
: Keeping functionality while streamlining or otherwise improving function flowdocs
: Documentation for project or components
Commit scopes
The following scopes are known for this project. A Conventional Commits commit message may optionally use one of the following scopes or none:
mysql
: A change to how themysql
service component worksredis
: A change to how theredis
service component worksgrafana
: A change to how thegrafana
service component worksopsi_server
: A change to how theopsi_server
service component worksbuild
: Build-related changes such asDockerfile
fixes and features.mount
: Volume or bind mount-related changes.net
: Networking, IP addressing, routing changesmeta
: Affects the project's repo layout, file names etc.