Skip to content

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

  1. Copy docker-compose.yml and .env.sample from the repository to the server.
  2. Create .env from 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.

  1. Make sure the external network exists: docker network create proxy_public (if your reverse proxy doesn't create it).
  2. Start the stack:
docker compose pull
docker compose up -d
docker compose logs -f tis-worker

The worker waits for healthy tis-db, tis-cache and tis-cli, applies migrations and starts the scheduler and API.

  1. Note the admin token. The first start creates the admin token admin: set its secret with CONFIG_SEED_ADMIN_TOKEN in .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 when CONFIG_SEED_ABUSEIPDB_KEY, CONFIG_SEED_URLHAUS_KEY or CONFIG_SEED_MALWAREPATROL_RECEIPT are set. Installations from before v1.25.2 seeded a token whose secret is in the repository history – rotate it (token:rotate --id 1).

  2. Open the Web GUI at https://<host>/app (or the CLI at https://<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:
alias tis-cli="docker exec -it tis-cli node /app/src/cli/index.js"

CLI sign-in

Outside development/test the CLI requires a sign-in:

  1. 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_ISSUER and CONFIG_OIDC_CLIENT_ID are set; it is offered by default.
  2. Username / password – the name and 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:

  1. OAuth2/OpenID Provider for TIS – public client (no secret) unless you set CONFIG_OIDC_CLIENT_SECRET. Device endpoint: https://<authentik>/application/o/device/.
  2. Flow (Flows & Stages → Flows → Create): slug default-device-code-flow, designation Stage Configuration, authentication Require authentication. No stage bindings needed.
  3. Bind it to the brand (System → Brands → edit → Device code flow). This fixes the 404.
  4. Only with CONFIG_OIDC_ALLOWED_GROUPS: add a groups scope mapping and include groups in CONFIG_OIDC_SCOPES (e.g. openid profile email groups).

CONFIG_OIDC_ISSUER is the application issuer, https://<authentik>/application/o/<app-slug>/.

Upgrading

docker compose pull
docker compose up -d

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:

docker exec tis-db pg_dump -U "$CONFIG_DB_USER" "$CONFIG_DB_NAME" | gzip > tis-$(date +%F).sql.gz

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-worker uses GET /healthz (see below), tis-cli answers its ttyd token endpoint, tis-db uses pg_isready, tis-cache redis-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; 200 degraded when 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 via CONFIG_LOG_LEVEL.
  • Errors are reported to Sentry when CONFIG_SENTRY_DSN is set.
  • GET /api/v1/stats (public) returns an approximate indicator count, also shown on the landing page.