Docker¶
Published Images¶
Each version is published for linux/amd64 and linux/arm64 on the GitHub Container Registry and
Docker Hub. The same image works with SQLite and PostgreSQL: it includes the PostgreSQL driver.
docker pull ghcr.io/cyr-ius/security-review:latest # or docker.io/cyrius44/security-review
| Tag | Content |
|---|---|
X.Y.Z, X.Y, X, latest |
Version X.Y.Z (the latest stable version for latest) |
X.Y.Z-opengrep, X.Y-opengrep, X-opengrep, latest-opengrep |
Same version with the OpenGrep engine (about 250 MB more) and its default rules, installed at start-up |
X.Y.Z-pg, X.Y-pg, X-pg, latest-pg |
Deprecated: aliases of the same image, kept until the next major version (former PostgreSQL variant) |
X.Y.ZrcN (a, b, rc) |
Pre-release: applies only to this specific tag; never used for latest |
Use the -opengrep image for the Controller server only if you run OpenGrep rules: the MCP server
never runs OpenGrep and keeps the default image. To build them locally: docker build . or
docker build --target opengrep ..
The images include an SBOM and a provenance attestation (accessible via docker buildx imagetools inspect --format '{{ json .Provenance }}' IMAGE).
Choosing the process¶
The first argument given to the image chooses the process it starts:
| Command | Process |
|---|---|
mcp (default) |
MCP server (security-review-mcp serve), in streamable HTTP or in stdio depending on SECREVIEW_TRANSPORT (http by default, or stdio) |
mcp ARGS… |
security-review-mcp ARGS… (for example mcp enroll) |
controller |
Controller server (security-review-controller serve): REST API, web interface |
controller ARGS… |
a command of the Controller server (for example controller token list) |
docker run -d -p 127.0.0.1:8080:8000 ghcr.io/cyr-ius/security-review controller # Controller server
docker run -d -p 127.0.0.1:8000:8000 ghcr.io/cyr-ius/security-review mcp # MCP server (HTTP)
In stdio, the host of a developer (VS Code, Claude…) starts the container itself and talks to it
on stdin / stdout (local mode, local SQLite database in the
volume). In .mcp.json:
{
"mcpServers": {
"security-review": {
"type": "stdio",
"command": "docker",
"args": ["run", "-i", "--rm", "-e", "SECREVIEW_TRANSPORT=stdio",
"-v", "secreview-mcp:/var/lib/secreview",
"ghcr.io/cyr-ius/security-review:latest", "mcp"]
}
}
}
-i is required (the host writes on the standard input of the container). In stdio, the server
refuses a PostgreSQL SECREVIEW_DATABASE_URL: to work on the database of the team, connect the
host to the shared MCP server in HTTP. Other arguments are passed to security-review-mcp, and
--entrypoint security-review-controller still works.
Docker Compose with PostgreSQL¶
One file,
examples/docker/docker-compose.yml,
starts everything with the published image: PostgreSQL, the migration of the schema, the
Controller server and the MCP server. The commands that download it and start it are in
Deploying the server.
The command printf writes the .env file, readable by you only: the password of PostgreSQL
and the secret of the web sessions, generated at random. docker compose up -d --wait returns
once every service is ready. The web interface answers on http://127.0.0.1:8080/ui/, the MCP
endpoint on http://127.0.0.1:8000/mcp.
Content of the files of version v11.0.0:
# security-review with PostgreSQL, ready to use: download this file in an empty folder,
# create the .env file next to it, then start everything.
#
# (umask 077 && printf 'POSTGRES_PASSWORD=%s\nSECREVIEW_SECRET_KEY=%s\n' \
# "$(openssl rand -hex 24)" "$(openssl rand -hex 32)" > .env)
# docker compose up -d --wait
# docker compose exec controller security-review-controller user reset-password admin
#
# Web interface: http://127.0.0.1:8080/ui/ MCP endpoint: http://127.0.0.1:8000/mcp
#
# Three services:
# db PostgreSQL (official image), reachable by the other services only
# controller web interface and REST API (for people); creates or updates the tables at start
# mcp MCP server (for the AI agents and the CI), started once the controller is ready
#
# The two ports are published on every interface of the server, for the team: enable TLS (the
# commented TLS lines of controller and mcp) or put a reverse proxy in front (see the
# documentation, page Docker), plain HTTP exposes the tokens.
# To keep a port on this machine only, prefix it with 127.0.0.1 ("127.0.0.1:8000:8000").
# Inside their containers, the servers always listen on every interface (set by the image):
# only the ports choose the exposure.
name: security-review
services:
db:
image: postgres:18-alpine
restart: unless-stopped
environment:
POSTGRES_DB: secreview
POSTGRES_USER: secreview
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
# Since PostgreSQL 18, the data is kept under /var/lib/postgresql/18/docker
- secreview-pg:/var/lib/postgresql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U secreview -d secreview"]
interval: 10s
timeout: 5s
retries: 5
security_opt:
- no-new-privileges:true
controller:
image: ghcr.io/cyr-ius/security-review:latest
restart: unless-stopped
# The image starts the Controller server with the `controller` command
command: ["controller"]
environment:
SECREVIEW_DATABASE_URL: postgresql+psycopg2://secreview:${POSTGRES_PASSWORD}@db:5432/secreview
# Signs the web sessions: keep the same value, or every session ends at restart
SECREVIEW_SECRET_KEY: ${SECREVIEW_SECRET_KEY}
# Public root URL of the controller (SECREVIEW_PUBLIC_URL=https://… in .env), copied
# to the database at start: the mcp service gives its web interface (/ui) to the agents
# without a token. Empty, it can be set in the web interface (General).
SECREVIEW_PUBLIC_URL: ${SECREVIEW_PUBLIC_URL:-}
# OAuth login of the MCP clients: the hosts sign in through the browser instead of a
# personal MCP token (needs SECREVIEW_PUBLIC_URL in https). Behind a reverse proxy,
# publish /.well-known/oauth-authorization-server, /authorize, /token, /register and
# /revoke to this service, /mcp and /.well-known/oauth-protected-resource to the mcp one.
# SECREVIEW_MCP_OAUTH_ENABLED: "true"
# SECREVIEW_MCP_URL: https://mcp.secreview.example.com/mcp
# Daily download of the OSV vulnerability advisories (needs Internet access)
# SECREVIEW_OSV_AUTO_UPDATE: "true"
# SECREVIEW_OSV_ECOSYSTEMS: PyPI,npm,Maven
# SECREVIEW_TLS_CERTFILE: /etc/secreview/tls/cert.pem
# SECREVIEW_TLS_KEYFILE: /etc/secreview/tls/key.pem
# volumes:
# - ./tls:/etc/secreview/tls:ro
ports:
- "8080:8000"
depends_on:
db:
condition: service_healthy
read_only: true
tmpfs:
- /tmp
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
mcp:
image: ghcr.io/cyr-ius/security-review:latest
restart: unless-stopped
# The image starts the MCP server with the `mcp` command (its default): streamable HTTP on
# /mcp (SECREVIEW_TRANSPORT)
command: ["mcp"]
environment:
SECREVIEW_DATABASE_URL: postgresql+psycopg2://secreview:${POSTGRES_PASSWORD}@db:5432/secreview
# SECREVIEW_TLS_CERTFILE: /etc/secreview/tls/cert.pem
# SECREVIEW_TLS_KEYFILE: /etc/secreview/tls/key.pem
# volumes:
# - ./tls:/etc/secreview/tls:ro
ports:
- "8000:8000"
depends_on:
# The MCP server only checks the tables: it starts once the controller has migrated them
controller:
condition: service_healthy
read_only: true
tmpfs:
- /tmp
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
volumes:
secreview-pg:
POSTGRES_PASSWORD=<48 hexadecimal characters>
SECREVIEW_SECRET_KEY=<64 hexadecimal characters>
| Service | Role | Listens on |
|---|---|---|
db |
PostgreSQL, reachable by the other services only | — |
controller |
Web interface (/ui/) and REST API, for people; creates or updates the tables at start |
8080 |
mcp |
MCP server (/mcp), for the agents |
8000 |
Good to know:
- Order of start-up.
controllerwaits until PostgreSQL is ready, then creates or updates the tables;mcp, which only checks them, waits untilcontrolleris healthy. After an image update,docker compose up -dis enough: the Controller server applies the new migrations. - Keep
.env. It holds the password of the database and the secret of the sessions; never commit it. The password is inserted as is in the connection URL: keep hexadecimal characters, as generated above. - Open to the team. The ports are published on every interface of the server, in plain
HTTP: enable the built-in TLS or put a reverse proxy in front before the team
connects. To keep a port on the server only (behind a reverse proxy on the same machine, for
example), prefix it with
127.0.0.1("127.0.0.1:8000:8000"). Only theportschoose the exposure: inside its container, each process listens on every interface (SECREVIEW_HOST=0.0.0.0, set by the image, not to be changed). For the built-in TLS, uncomment incontrollerandmcpthe TLS variables (SECREVIEW_TLS_CERTFILE,SECREVIEW_TLS_KEYFILE) and thevolumesthat mounts the certificate files read-only from./tls(see Certificate Authority). The Controller server exposes no write route without a token: it can stay internal, unless the OAuth login of MCP clients is on: the MCP hosts then reach its OAuth routes (uncommentSECREVIEW_MCP_OAUTH_ENABLEDandSECREVIEW_MCP_URLincontroller; behind a single reverse proxy host, route/mcpand/.well-known/oauth-protected-resourcetomcp, everything else tocontroller). Add to.envthe public root URL of the Controller server (SECREVIEW_PUBLIC_URL=https://…): the Controller server copies it to the database at start, so that the MCP server gives the link of the web interface (/ui) to an agent without a token, and the connectors link their tickets to the reports. Without it, set it on the General page. - Vulnerability advisories. The OSV database is empty at the first start: the dependency
analysis answers that no database is loaded. To let the Controller server download it at start-up,
then every 24 hours, uncomment
SECREVIEW_OSV_AUTO_UPDATE(andSECREVIEW_OSV_ECOSYSTEMS) in thecontrollerservice, then rundocker compose up -d. Follow the import withdocker compose logs -f controller. Without Internet access, import an archive withadvisories import(see Dependencies). - Hardening. The containers of Security Review run with a read-only file system and without any Linux capability.
- Data. The
secreview-pgvolume holds the reports, the accounts and the tokens: back it up (see Backing up PostgreSQL). It is mounted on/var/lib/postgresql, where PostgreSQL 18 keeps its data; a volume created by PostgreSQL 17 does not start with this image (see Upgrading PostgreSQL).
The administration commands run in the Controller container:
docker compose exec controller security-review-controller token list
Without Docker Compose¶
The same image runs each process in its own container, on an existing PostgreSQL database (see Architecture):
- the MCP server is started with the
mcpcommand (the default), in streamable HTTP; - the Controller server (REST API, web interface, administration) is started with the
controllercommand, with the built-in tokens and TLS or behind a reverse proxy that handles them.
DB=postgresql+psycopg2://user:pass@db:5432/secreview
# 1. Controller server: web interface and API, preferably on an internal network; creates or
# updates the schema (Alembic) at start, so start it first
docker run -d --name secreview-controller -p 127.0.0.1:8080:8000 \
-e SECREVIEW_DATABASE_URL=$DB \
ghcr.io/cyr-ius/security-review controller
# 2. MCP server: exposed to the agents and the CI
docker run -d --name secreview-mcp -p 8443:8443 \
-e SECREVIEW_DATABASE_URL=$DB \
-v ./tls:/etc/secreview/tls:ro \
-e SECREVIEW_TLS_CERTFILE=/etc/secreview/tls/cert.pem \
-e SECREVIEW_TLS_KEYFILE=/etc/secreview/tls/key.pem \
ghcr.io/cyr-ius/security-review mcp serve --port 8443
# 3. Password of the admin account, created at the first start of the Controller server
docker exec secreview-controller security-review-controller user reset-password admin
In the image, both processes listen on every interface of the container (SECREVIEW_HOST=0.0.0.0),
port 8000 unless --port is given: the -p option chooses the address and the port of the
host (-p 127.0.0.1:8080:8000: this machine only). Mounted files must be readable by the user of
the container (UID 10001). The MCP server
refuses to start while the schema is not migrated (step 1).
Internal Rules and CLI¶
Rules are stored in the database; you can import a directory of TOML packs using the Controller server CLI (running servers will apply them within seconds):
docker run --rm -v secreview-data:/var/lib/secreview -v ./rules.d:/rules:ro \
ghcr.io/cyr-ius/security-review controller rules import /rules
Other commands can be executed in the same way:
docker run --rm -v secreview-data:/var/lib/secreview \
ghcr.io/cyr-ius/security-review controller token list