Shinobi

Home  /  Installation  /  Docker

Installing Shinobi with Docker

There are three Docker images for Shinobi and they do not behave the same way. This page documents the image built from the main Shinobi repository, which bundles MariaDB inside the same container: an integrated database with simple volume mounts. If you want a separate database container, use ShinobiDocker instead, covered further down.

The important differences are what ends up on your host filesystem and what updating actually does.

  • This image (registry.gitlab.com/shinobi-systems/shinobi) : MariaDB runs inside the same container. The application ships in the image and only seeds /home/Shinobi when that folder is empty, so you can mount the whole directory or just the parts you want to keep. Update by pulling the image and recreating the container.
  • ShinobiDocker :latest (master branch) : separate database container. The application is copied onto /home/Shinobi on every start, so the source and node_modules live on your disk alongside your data.
  • ShinobiDocker :v2 (v2 branch) : separate database container. The application stays inside the image and only your data is kept on the host, in a single folder. docker compose pull is a real update.

If you already run Shinobi in Docker, nothing here changes underneath you. The :latest tag and the master branch are unchanged. Moving to v2 is opt-in and needs a small edit to your compose file.

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
  • Exposed ports are 8080 (web UI/API), 443, 21, and 25.
  • On Linux, also mount shared memory for live streams and pass through the host timezone: -v "/dev/shm/shinobiStreams":'/dev/shm/streams':'rw' -v '/etc/localtime':'/etc/localtime':'ro'.
  • Log in at 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).
  • The 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
}
  • These defaults (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.
  • The actual database files live at /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.
  • This directory must be treated like a live database datadir: copy it while the container is stopped, or use 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:

  • Globally, with additional storage locations — add entries to the addStorage array in conf.json, then choose one as a monitor's storage location from the dashboard:
  • Per-Monitor override — a monitor's own 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:

  • Superuser panel — log in at 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.
  • Directly on the host — edit $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.

  • Stop the container first, so the database and any open files are in a consistent state : docker stop Shinobi.
  • Copy $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/.
  • Copy $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.
  • If any monitor uses a details.dir path outside /home/Shinobi, copy that storage location too, and re-create the same bind mount on the new host.
  • Preserve 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.
  • On the new host, run the same 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.

ShinobiDocker runs the database in its own container rather than bundling it. Its master branch publishes registry.gitlab.com/shinobi-systems/shinobidocker:latest and uses the older layout: the application is cloned to /opt/shinobi at build time, then copied onto /home/Shinobi every time the container starts. That is why the application source and node_modules appear in the folder you mount. The copy only adds files and never removes them, so an install that has been through several versions keeps leftovers from older ones.

The v2 branch replaces that arrangement. Both remain available, and :latest is not being changed.

In v2 the application stays inside the container and only your data is kept on the host, in one folder. This is the conventional Docker arrangement, and it makes pulling a new image a genuine update rather than something that has to rewrite files on your disk.

The v2 layout lives on the v2 branch. The default master branch is still the previous layout, so clone the branch explicitly.

git clone -b v2 https://gitlab.com/Shinobi-Systems/ShinobiDocker.git
cd ShinobiDocker
bash setup_and_run.sh

Published images are in the ShinobiDocker container registry as registry.gitlab.com/shinobi-systems/shinobidocker:v2, plus :v2-nvidia, :v2-arm64v8 and :v2-arm32v7.

Open port 8080 of your Docker host in a browser once it is up. The default superuser is [email protected] with password admin, at /super. Change it. Those credentials are stored in data/super.json.

One mount, /config, mapped to ./data by default. Everything underneath it belongs to you.

data/
  conf.json           general configuration
  super.json          superuser credentials
  videos/             recordings
  plugins/            installed plugins
  customAutoLoad/     custom dashboard modules
  ssl/                server.key / server.cert
  faces/              face recognition library

These are linked back into the application directory when the container starts, because Shinobi resolves them relative to its working directory. It is an implementation detail, but it is the reason a single folder is now enough. Back up data/ and your database and you have backed up the install.

docker compose -f docker-compose-main.yml pull
docker compose -f docker-compose-main.yml up -d

Your data/ directory is untouched. The update button inside the dashboard is disabled in v2, since changing a container's filesystem in place works against the image being the unit of deployment.

You do not have to do this. The previous layout stays on the master branch and on :latest, and a git pull on master will not bring you across. If your install works, leaving it alone is a valid choice.

If you do want to move, copy your data into the new location first, from the old $HOME/Shinobi directory:

mkdir -p ./data
cp    $HOME/Shinobi/conf.json           ./data/
cp    $HOME/Shinobi/super.json          ./data/
cp -r $HOME/Shinobi/videos              ./data/
cp -r $HOME/Shinobi/plugins             ./data/
cp -r $HOME/Shinobi/libs/customAutoLoad ./data/customAutoLoad
cp -r $HOME/Shinobi/ssl                 ./data/

Then replace the old volume line with - ./data:/config. Recordings are usually the bulk of it, so you may prefer to move that folder rather than copy it, or mount it straight at /config/videos. Your database is unaffected: point the new container at the same one and your monitors, users and recording index all come across as they are.

  • If your compose file still mounts data at /home/Shinobi, the container stops rather than starting. Otherwise it would come up as a new install with the default superuser and an empty videos directory, which looks exactly like losing your recordings.
  • If you mount a full old installation at /config, it also stops, because that is an application directory rather than a data directory.
  • SHINOBI_UPDATE no longer does anything and warns if set. HOME=/home/Shinobi can be removed from your compose file.
  • Nothing is deleted in any of these cases. Your data stays on the host exactly where it was.

Plugin pairing works the same way described in Connecting Plugins (Docker) above: the plugin dials Shinobi, so it needs a reachable HOST and PORT, and a PLUGIN_KEY matching pluginKeys in Shinobi's conf.json. With compose you can put both containers on the same network and address Shinobi by its service name, in which case PORT is Shinobi's port inside the container rather than whatever you published.

# docker-compose-plugin.yml
services:
  yolo-v9-onnx:
    image: registry.gitlab.com/shinobi-systems/shinobi-plugins/yolo-v9-onnx:cpu
    container_name: shinobi-plugin-yolo-v9-onnx
    environment:
      - HOST=shinobi          # the Shinobi service name, NOT localhost
      - PORT=8080             # Shinobi's port INSIDE the container
      - PLUGIN_NAME=YoloV9
      - PLUGIN_KEY=change_me_to_a_long_random_string
      - ACCELERATION=cpu
    restart: unless-stopped

Set the same key on the Shinobi service with PLUGIN_KEYS={"YoloV9":"change_me_to_a_long_random_string"}, or edit pluginKeys in data/conf.json. The environment variable is merged into the existing configuration, so it will not clobber keys the Plugin Manager generated for other plugins. Then bring both up together and check the log.

docker compose -f docker-compose-main.yml -f docker-compose-plugin.yml up -d
docker logs shinobi | grep "Connected to plugin"

For a GPU plugin use the :cuda, :rocm or :openvino tag, set ACCELERATION to match, and pass the device through with a deploy.resources.reservations.devices entry. As with the other images, the host still needs the matching driver and container toolkit installed.

One caveat if you use the Plugin Manager's Pull & Run button while Shinobi is itself inside a container: the plugin is started as a sibling container on the default bridge network and pointed at the address Shinobi detects for itself, which is on a different, isolated network. Set the host override in the deploy dialog to an address the sibling can actually reach, normally the Docker host's LAN IP, and remember the port then has to be the one you published rather than 8080. Defining the plugin in compose as above avoids the issue.

ShinobiDocs

All content is property of their respective owners.