Shinobi ships a single Dockerfile that bundles MariaDB inside the same container — an integrated database with simple volume mounts. If you need a separate database container or a more elaborate topology, use the ShinobiDocker registry instead.
To run the pre-built image directly with docker run (Shinobi is published to registry.gitlab.com/shinobi-systems/shinobi:dev, no longer to Docker Hub):
docker run -d --name='Shinobi' --memory=2g \
-p '8080:8080/tcp' -p '21:21/tcp' \
-v "$HOME/ShinobiDatabase":'/var/lib/mysql':'rw' \
-v "$HOME/Shinobi":'/home/Shinobi':'rw' \
registry.gitlab.com/shinobi-systems/shinobi:dev
8080 (web UI/API), 443, 21, and 25.
-v "/dev/shm/shinobiStreams":'/dev/shm/streams':'rw' -v '/etc/localtime':'/etc/localtime':'ro'.
http://YOUR_SHINOBI:8080/super to reach the Superuser panel once the container is running.
To build the image from source instead of pulling the pre-built one:
git clone -b dev https://gitlab.com/Shinobi-Systems/Shinobi.git ShinobiSource
cd ShinobiSource
docker build --tag shinobi-image:1.0 .
docker run -d --name='Shinobi' --memory=2g \
-p '8080:8080/tcp' -p '21:21/tcp' \
-v "$HOME/ShinobiDatabase":'/var/lib/mysql':'rw' \
-v "$HOME/Shinobi":'/home/Shinobi':'rw' \
shinobi-image:1.0
A Dockerized Shinobi should connect to plugins that are themselves running as separate Docker containers, rather than the Worker or Bare Metal methods, since those run against the host's own process/filesystem — not this container's. Every plugin under shinobi-plugins that ships a docker/ folder can be pulled and run this way, either through the Plugin Manager's Pull & Run (Docker) button, or manually. Here's an example using YoloV9 Object Detection (yolo-v9-onnx), which supports CPU, NVIDIA (CUDA), AMD (ROCm), and Intel (OpenVINO) acceleration images.
# CPU
docker run --name shinobi-plugin-yolov9-onnx --restart unless-stopped \
-e HOST=your.shinobi.host -e PORT=8080 -e PLUGIN_KEY=yourkey \
registry.gitlab.com/shinobi-systems/shinobi-plugins/yolo-v9-onnx:cpu
# NVIDIA (CUDA)
docker run --name shinobi-plugin-yolov9-onnx --restart unless-stopped --gpus all \
-e HOST=your.shinobi.host -e PORT=8080 -e PLUGIN_KEY=yourkey \
registry.gitlab.com/shinobi-systems/shinobi-plugins/yolo-v9-onnx:cuda
HOST / PORT point the plugin at the Shinobi container — if Shinobi is on the same Docker host, this is the host's LAN IP and the port mapped to 8080, not localhost.
PLUGIN_KEY is the pairing key — it must match a key set under pluginKeys in Shinobi's conf.json (see Pairing below).
cuda/rocm/openvino images additionally need the matching GPU driver and container toolkit already set up on the Docker host itself — the image only ships the userspace runtime libraries, not the driver.
Using the Plugin Manager's Pull & Run (Docker) button instead handles the pairing key and host address automatically, without needing to run docker run by hand at all. Either way, once paired, the plugin's status appears under the Plugin Manager the same as any other running plugin.
If you need to pair a plugin container manually, open the Superuser panel's Configuration tab and add its key under pluginKeys:
"pluginKeys": {
"yourkey": "YoloV9 Object Detection"
}
The Dockerfile declares two volumes, conventionally bind-mounted as shown in the docker run command above:
$HOME/Shinobi → /home/Shinobi — the entire app directory: conf.json, super.json, the videos/ folder, the plugins/ folder and libs/customAutoLoad/.
$HOME/ShinobiDatabase → /var/lib/mysql — the MariaDB data directory (see Database below).
$HOME/Shinobi/config → /config (optional) — where the SSL keypair is kept when SSL_ENABLED=true. Note this is a top-level /config in the container, not a folder inside /home/Shinobi, so it needs its own -v flag if you want the certificate to survive the container.
/dev/shm/shinobiStreams → /dev/shm/streams (Linux only, recommended) — temporary live-stream segment storage. This must exist in RAM; it's scratch space, not something you need to back up.
/etc/localtime → /etc/localtime (read-only) — passes the host's timezone into the container.
conf.json and super.json are not baked into the image — on first start, the container's entrypoint copies conf.sample.json/super.sample.json to conf.json/super.json inside the mounted /home/Shinobi directory if they don't already exist there, so they persist across container restarts and rebuilds.
/home/Shinobi is a slightly unusual mount point, because it is also where the application itself lives inside the image. Mounting a host directory there hides whatever the image shipped at that path, and Docker only auto-populates named volumes from the image — never bind mounts. Pointing an empty host folder at it would therefore leave the container with no application at all.
Shinobi handles this for you. The image keeps a pristine copy of the application at /opt/shinobi-defaults, and on start the entrypoint checks for /home/Shinobi/camera.js. If it is missing, the mount is treated as empty and seeded from that copy before anything else runs — which is why the docker run command above works against a brand new, empty host directory. The startup files themselves (the entrypoint and the pm2 config) are stored outside /home/Shinobi so that they can never be hidden by your mount.
If you would rather not keep a copy of the application source on the host, mount only the folders you actually want to persist instead:
docker run -d --name='Shinobi' --memory=2g \
-p '8080:8080/tcp' -p '21:21/tcp' \
-v "$HOME/ShinobiDatabase":'/var/lib/mysql':'rw' \
-v "$HOME/Shinobi/videos":'/home/Shinobi/videos':'rw' \
-v "$HOME/Shinobi/plugins":'/home/Shinobi/plugins':'rw' \
-v "$HOME/Shinobi/customAutoLoad":'/home/Shinobi/libs/customAutoLoad':'rw' \
-v "$HOME/Shinobi/config":'/config':'rw' \
registry.gitlab.com/shinobi-systems/shinobi:dev
The trade-off is that conf.json and super.json then stay inside the container, so you can no longer edit them with a text editor on the host — use the Superuser panel, reach them with docker exec, or go back to mounting the whole of /home/Shinobi.
By default Shinobi uses MySQL/MariaDB, and this Dockerfile installs MariaDB directly inside the same container image (an integrated database, not a separate DB container). On first start, the entrypoint initializes /var/lib/mysql, starts mysqld, and creates the configured database user/schema, then writes the connection info into conf.json's db object:
"db": {
"host": "127.0.0.1",
"user": "majesticflame",
"password": "",
"database": "ccio",
"port": 3306
}
DB_USER, DB_PASSWORD, DB_HOST, DB_DATABASE, DB_PORT, DB_TYPE) come from the Dockerfile's build-time ENV values, merged into conf.json on every container start.
/var/lib/mysql inside the container, which is the $HOME/ShinobiDatabase host mount — this is where monitors, users, events, and plugin key records are stored.
mysqldump, rather than copying files while MariaDB is running.
The default video directory is set by videosDir in conf.json, which resolves to videos/ inside Shinobi's own install directory — i.e. /home/Shinobi/videos in the container, which is already covered by the $HOME/Shinobi host mount.
"videosDir": "__DIR__/videos"
Recordings for a monitor are written to <videosDir>/<group key>/<monitor id>/. There are two ways to point storage elsewhere:
addStorage array in conf.json, then choose one as a monitor's storage location from the dashboard:
details.dir field, set from its Edit Monitor screen, takes priority over the global videosDir/addStorage defaults entirely.
"addStorage": [
{"name":"second","path":"__DIR__/videos2"}
],
If you point a monitor's storage at a path outside /home/Shinobi — e.g. a separate large disk — you must bind-mount that path into the container yourself (add another -v flag to the docker run command), and it will need to be backed up separately from the $HOME/Shinobi mount.
Environment-variable configuration has been disabled for anything beyond the build-time DB_*/SSL_*/PLUGIN_KEYS/SUBSCRIPTION_ID values. Everything else is changed directly in conf.json, which lives on the host at $HOME/Shinobi/conf.json since /home/Shinobi is volume-mounted. There are two ways to edit it:
http://YOUR_SHINOBI:8080/super, open the Configuration tab, and edit conf.json as regular JSON in the top bar. Saving writes straight back to the file on disk.
$HOME/Shinobi/conf.json with any text editor. If it doesn't exist yet, create it (or start the container once so the entrypoint copies conf.sample.json to conf.json for you).
Restart the container after manual edits to conf.json to make sure any settings that are only read at startup take effect: docker restart Shinobi.
Since this Dockerfile keeps everything Shinobi-specific inside the directories you mount, migrating to a new host is mostly a matter of copying those directories across and starting the same image there. For a standard install that means $HOME/Shinobi and $HOME/ShinobiDatabase, plus whatever you mounted at /config if you enabled SSL.
docker stop Shinobi.
$HOME/Shinobi in full — this carries conf.json (DB connection info, videosDir/addStorage paths, pluginKeys, mail and cloud-upload settings, SSL config), super.json (Superuser credentials), the videos/ folder (and any addStorage subfolders), the plugins/ folder, and libs/customAutoLoad/.
$HOME/ShinobiDatabase in full (or take a mysqldump of the ccio/DB_DATABASE database and restore it on the new host) — this carries every monitor, user, event, and plugin pairing record.
details.dir path outside /home/Shinobi, copy that storage location too, and re-create the same bind mount on the new host.
server.key and server.cert from whatever you mounted at /config/ssl if SSL_ENABLED is used — if these are missing on first start, the entrypoint generates a brand-new self-signed certificate with different key material, which will not match certificates already trusted by clients. Remember this is the top-level /config mount, so it is not covered by copying $HOME/Shinobi alone.
docker run command (same image/tag) pointing -v at the copied $HOME/Shinobi and $HOME/ShinobiDatabase paths. The /dev/shm/shinobiStreams and /etc/localtime mounts don't need copying — just re-create them equivalently on the new host.