Installation & Deployment¶
TIS runs as a Docker Compose stack. The image is built by GitLab CI from release tags and published as registry.cap-net.cz/capnet/threat-intel-scrany:<version> and :latest.
Services¶
| Service | Role |
|---|---|
tis-worker |
The daemon: scheduler (feed fetching, pruning) and the REST API on CONFIG_API_PORT (3000). Runs database migrations on every start (npm start → prestart). Proxies /cli to tis-cli. Attached to the external proxy_public network so a reverse proxy can publish it. |
tis-cli |
ttyd web terminal running the interactive CLI, served under CONFIG_CLI_PATH (/cli). |
tis-db |
Postgres; data in the tis-db volume. |
tis-cache |
Redis (no persistence): export cache, CLI sessions, live schedule resync between CLI/API and worker. |
No port is published to the host; expose tis-worker:3000 through the reverse proxy on proxy_public (TLS terminates there).
First installation¶
- Copy
docker-compose.ymland.env.samplefrom the repository to the server. - Create
.envfrom the sample and set at least:
NODE_ENV=production
CONFIG_API_PORT=3000
CONFIG_DB_USER=tis
CONFIG_DB_PASSWORD=<strong password>
CONFIG_DB_NAME=tis
CONFIG_CLI_PATH=/cli
CONFIG_CLI_PORT=7681
CONFIG_REDIS_PORT=6379
CONFIG_OIDC_ISSUER=https://auth.example.com/application/o/tis/
CONFIG_OIDC_CLIENT_ID=<device-flow client id>
All settings are described in Configuration.
- Make sure the external network exists:
docker network create proxy_public(if your reverse proxy doesn't create it). - Start the stack:
The worker waits for healthy tis-db, tis-cache and tis-cli, applies migrations and starts the scheduler and API.
-
Note the admin token. The first start creates the admin token
admin: set its secret withCONFIG_SEED_ADMIN_TOKENin.env, or take the random one printed once in the log (docker compose logs tis-worker | grep "admin API token"). Example feeds (AbuseIPDB, URLhaus, MalwarePatrol) are only created whenCONFIG_SEED_ABUSEIPDB_KEY,CONFIG_SEED_URLHAUS_KEYorCONFIG_SEED_MALWAREPATROL_RECEIPTare set. Installations from before v1.25.2 seeded a token whose secret is in the repository history – rotate it (token:rotate --id 1). -
Open the Web GUI at
https://<host>/app(or the CLI athttps://<host>/cli), sign in and add your sources. For Authentik sign-in in the GUI, add its redirect URI to the provider – see Web GUI.
Accessing the CLI¶
- Browser:
https://<host>/cli– the worker proxies it to the ttyd container. - Shell on the server:
docker exec -it tis-cli node /app/src/cli/index.js, or define an alias:
CLI sign-in¶
Outside development/test the CLI requires a sign-in:
- Authentik (SSO) via the OAuth 2.0 Device Authorization Grant – the CLI prints a URL and a code, you approve it in the browser. Used when
CONFIG_OIDC_ISSUERandCONFIG_OIDC_CLIENT_IDare set; it is offered by default. - Username / password – the
nameand secret of an admin API token. The only option when OIDC isn't configured.
If CONFIG_OIDC_ALLOWED_GROUPS is set, the user must be in one of those groups (groups claim). With Redis available, the session is kept for CONFIG_SESSION_TTL seconds (default 24 h); logout ends it.
Authentik setup¶
Authentik has no default flow for the device endpoint, so the verification URL returns 404 until you create one:
- OAuth2/OpenID Provider for TIS – public client (no secret) unless you set
CONFIG_OIDC_CLIENT_SECRET. Device endpoint:https://<authentik>/application/o/device/. - Flow (Flows & Stages → Flows → Create): slug
default-device-code-flow, designation Stage Configuration, authentication Require authentication. No stage bindings needed. - Bind it to the brand (System → Brands → edit → Device code flow). This fixes the 404.
- Only with
CONFIG_OIDC_ALLOWED_GROUPS: add agroupsscope mapping and includegroupsinCONFIG_OIDC_SCOPES(e.g.openid profile email groups).
CONFIG_OIDC_ISSUER is the application issuer, https://<authentik>/application/o/<app-slug>/.
Upgrading¶
Migrations run automatically when tis-worker starts. Read the release notes (GitLab releases, generated from commit messages) first and back up the database before upgrades that contain migrations:
Notable migrations:
| Migration | Note |
|---|---|
20261012000000-lowercase-fqdn |
Irreversible. Merges domain indicators that differ only in letter case (per source) and lowercases all domains. |
20261012100000-add-indicator-permanent, 20261012110000-add-source-manual, 20261012120000-create-groups |
New columns/tables only. |
20261013000000-unique-live-names |
Source and token names must be unique among live rows only, so names of deleted ones can be reused. |
20261013100000-add-json-indicator-type |
Adds the missing json indicator type; JSON indicators could not be stored before. |
20261014000000-create-audit-logs, 20261014100000-indicator-network-index |
Audit log table; GiST index for network lookups. |
The worker shuts down gracefully on SIGTERM (docker stop): running fetches get up to ~4 s to finish, in-flight HTTP requests complete, connections close.
Migrating from SQLite¶
Versions before the Postgres switch stored data in SQLite. To move the data:
# 1. create the Postgres schema
CONFIG_DB_URL=postgres://tis:tis@localhost:5432/tis npx sequelize-cli db:migrate
# 2. copy the data
CONFIG_DB_URL=postgres://tis:tis@localhost:5432/tis node scripts/migrate-sqlite-to-psql.cjs --sqlite data/production.sqlite
Health and monitoring¶
- Docker healthchecks:
tis-workerusesGET /healthz(see below),tis-clianswers its ttyd token endpoint,tis-dbusespg_isready,tis-cacheredis-cli ping. GET /healthz(no token, not logged) checks the database and Redis:{ "status": "ok" | "degraded" | "error", "version", "checks": { "db", "redis" } }. 503 when the database is unreachable; 200degradedwhen only Redis is down (no cache, in-memory GUI sessions, no live resync). Point external monitoring at it too.- Logs:
docker compose logs -f tis-worker– fetch results ([CRON SUCCESS] … Ingested: +N, Updated: ~N, Skipped: !N), pruning, API requests (tokens are redacted). Level viaCONFIG_LOG_LEVEL. - Errors are reported to Sentry when
CONFIG_SENTRY_DSNis set. GET /api/v1/stats(public) returns an approximate indicator count, also shown on the landing page.