Profiles Configuration
File: profiles.yml
Basic Structure
Section titled “Basic Structure”# Default profile to usedefault_profile: prod
# Database profilesprofiles: prod: type: clickhouse host: localhost port: 9000 # ... database-specific settings
# Alert channelsalert_channels: mattermost_ops: type: mattermost webhook_url: "https://mattermost.example.com/hooks/xxx" # ... channel-specific settingsA metric or project can also reference a second profile by name via
source_profile to read metric SQL from one database while _dtk_* state
stays in another — see the Hybrid Mode guide.
Database Profiles
Section titled “Database Profiles”ClickHouse, PostgreSQL, MySQL, MariaDB, and DuckDB are all fully supported as
state backends (they hold _dtk_* state and can also run metric SQL). The
connection fields differ per backend (ClickHouse/MySQL/MariaDB use
databases; PostgreSQL and DuckDB use schemas — PostgreSQL connects to
a database, DuckDB opens a file at path). See the per-backend Databases
guide for a focused walkthrough of each. MariaDB uses the
MySQL backend — set type: mariadb (an alias with identical fields) or keep
type: mysql against a MariaDB server; either way the vendor is
auto-detected at connect. See the MySQL guide
for the MariaDB-specific notes, and the DuckDB
guide for its
single-writer caveat.
Snowflake (type: snowflake) is a source-only backend: it can only be
referenced as a hybrid-mode source_profile, never as the
state profile — see the Snowflake profile
below and the Snowflake guide.
BigQuery (type: bigquery) is likewise a source-only backend: it can
only be referenced as a hybrid-mode source_profile, never as
the state profile — see the BigQuery profile
below and the BigQuery guide.
ClickHouse Profile
Section titled “ClickHouse Profile”profiles: prod: type: clickhouse host: clickhouse.example.com port: 9000 user: default password: "your_password"
# Internal tables location (for _dtk_* tables) internal_database: analytics
# Data tables location (for your metrics) data_database: default
# ClickHouse-specific settings settings: max_execution_time: 600 max_memory_usage: 10000000000Required fields:
type: Must be"clickhouse"host: ClickHouse server hostnameport: ClickHouse native protocol port (default: 9000)internal_database: Database for dtk* tablesdata_database: Database for data queries
Optional fields:
user: Username (default:"default")password: Password (default: empty string)settings: Dict of ClickHouse settings to apply
PostgreSQL Profile
Section titled “PostgreSQL Profile”PostgreSQL connects to a database and stores tables in schemas inside it.
The database must already exist; detectkit creates the schemas.
profiles: prod: type: postgres host: localhost port: 5432 user: postgres password: "your_password"
database: detectkit # database to connect to (must already exist) internal_schema: detectkit # schema for _dtk_* tables (auto-created) data_schema: public # schema for your data queriesRequired fields:
type: Must be"postgres"host: PostgreSQL server hostnameport: PostgreSQL port (default: 5432)database: Database to connect to (must already exist)internal_schema: Schema for dtk* tables (detectkit creates it)data_schema: Schema for data queries
Optional fields:
user: Username (default:"default")password: Password (default: empty string)settings: Extrapsycopg2.connectkeyword arguments
MySQL Profile
Section titled “MySQL Profile”MySQL (8.0+) uses databases (no separate schema concept). MariaDB is
fully supported through this same backend — type: mariadb is an identical
alias, and type: mysql against a MariaDB server also works (the driver
detects the actual vendor at connect time, not from type). See the MySQL
guide → MariaDB for version support and the
detectkit[mariadb] install extra.
profiles: prod: type: mysql host: localhost port: 3306 user: root password: "your_password"
# Database locations (auto-created) internal_database: detectkit data_database: analyticsRequired fields:
type: Must be"mysql"host: MySQL server hostnameport: MySQL port (default: 3306)internal_database: Database for dtk* tables (detectkit creates it)data_database: Database for data queries
Optional fields:
user: Username (default:"default")password: Password (default: empty string)database: Optional default database for the connectionsettings: Extrapymysql.connectkeyword arguments
DuckDB Profile
Section titled “DuckDB Profile”DuckDB is an in-process, single-file database — there’s no host, port,
user or password, just a file path (or :memory:). Internal/data tables
live in schemas inside that one file, same location model as PostgreSQL.
The same profile type also attaches MotherDuck (DuckDB’s serverless cloud)
when path is "md:<database>" — a full state backend over the same client.
See the DuckDB guide for the full walkthrough,
including the MotherDuck section and the
single read-write connection at a time caveat (local files only) before
pointing a scheduled dtk run and a long-lived dtk ui at the same file.
profiles: dev: type: duckdb path: "./detectkit.duckdb" # file path (created if it doesn't exist), or ":memory:"
internal_schema: detectkit # schema for _dtk_* tables (auto-created) data_schema: main # schema for your data queries (DuckDB's default schema)
cloud_state: type: duckdb path: "md:detectkit" # MotherDuck cloud database motherduck_token: "{{ env_var('MOTHERDUCK_TOKEN') }}" internal_schema: detectkit data_schema: mainRequired fields:
type: Must be"duckdb"path: Database file path (created if it doesn’t exist), the literal":memory:"(transient — tests/preview only, state is lost on exit), or"md:<database>"to attach a MotherDuck cloud database
Optional fields:
internal_schema(default:"detectkit") - Schema for_dtk_*tables (detectkit creates it)data_schema(default:"main") - Schema for data queriesmotherduck_token: MotherDuck service token formd:paths (env-interpolated, like every secret — e.g."{{ env_var('MOTHERDUCK_TOKEN') }}"); ignored for local file paths. Unset, themotherduckextension falls back to amotherduck_tokenenvironment variable. See the MotherDuck sectionread_only(default:false) - Open a local file read-only; required when another process already holds it read-write. Local-files-only — MotherDuck (md:paths) is served and doesn’t take DuckDB’s read-only attach flagsettings: Extraduckdb.connect(..., config=...)options (e.g.memory_limit)
Snowflake Profile (source-only)
Section titled “Snowflake Profile (source-only)”Snowflake is a source-only backend — a type: snowflake profile is valid
only as a hybrid-mode source_profile; detectkit refuses
to store _dtk_* state in it. See the Snowflake
guide for the full walkthrough, including key-pair
setup and the UTC/column-folding notes.
profiles: snowflake_wh: type: snowflake account: "ab12345.eu-central-1" # Snowflake account identifier user: DETECTKIT_SVC private_key_path: "./keys/detectkit_rsa_key.p8" # key-pair auth (recommended) private_key_passphrase: "{{ env_var('SNOWFLAKE_KEY_PASSPHRASE') }}" warehouse: MONITORING_WH # optional database: ANALYTICS # optional schema: PUBLIC # optional (session schema) role: DETECTKIT_ROLE # optionalRequired fields:
type: Must be"snowflake"account: Snowflake account identifier (e.g.ab12345.eu-central-1)user: Login name (must be set explicitly)private_key_pathorpassword: key-pair auth (recommended) or password
Optional fields:
private_key_passphrase: Passphrase for the PEM key (env-interpolatable)warehouse: Virtual warehouse to run queries ondatabase: Default database for the sessionschema: Default schema for the session (the YAML keyschemamaps to the session schema)role: Role to assume for the sessionsettings: Extra Snowflake session parameters (merged over detectkit’s — e.g.{TIMEZONE: "..."}to override the UTC session pin)
There is no host / port — Snowflake connects through its account-based
endpoint.
BigQuery Profile (source-only)
Section titled “BigQuery Profile (source-only)”BigQuery is a source-only backend — a type: bigquery profile is valid
only as a hybrid-mode source_profile; detectkit refuses
to store _dtk_* state in it. See the BigQuery
guide for the full walkthrough, including credential
setup and the TIMESTAMP / cost-guardrail notes.
profiles: bigquery_wh: type: bigquery project: my-analytics-project # GCP project billed for queries credentials_json_path: "/etc/detectkit/bq-sa.json" # service-account key (optional) location: EU # optional job location dataset: analytics # optional default dataset settings: maximum_bytes_billed: 1000000000 # optional cost guardrailRequired fields:
type: Must be"bigquery"project: GCP project id billed for the queries (e.g.my-analytics-project)
Optional fields:
credentials_json_path: Path to a service-account JSON key file. Unset → Application Default Credentials (gcloud ADC, an attached service account, or Workload Identity)location: Job location (e.g.EU); unset → BigQuery infers it from the referenced datasetsdataset: Default dataset so unqualified table names in the query resolveapi_endpoint: API endpoint override — for the BigQuery emulator (e.g.http://localhost:9050) or a private/regional endpoint; a plain-http://endpoint without a key file switches auth to anonymous credentials (the emulator path), whilehttps://endpoints authenticate normally via key file or ADCsettings: ExtraQueryJobConfigattributes applied to every query (e.g.maximum_bytes_billed,labels); unknown attribute names are rejected at connect
There is no host / port / user / password — BigQuery connects
through the Google client with the project and credentials above.
Alert Channels
Section titled “Alert Channels”Mattermost Channel
Section titled “Mattermost Channel”alert_channels: mattermost_ops: type: mattermost webhook_url: "https://mattermost.example.com/hooks/xxx" channel: "alerts" # Explicit channel name timeout: 10 # Request timeout (seconds) # Bot name + avatar default to the detectkit brand (override below). # username: "detectkit" # icon_url: "https://.../bot.png" # or icon_emoji: ":warning:"Required fields:
type: Must be"mattermost"webhook_url: Mattermost incoming webhook URL
Optional fields:
username(default:"detectkit") - Bot display nameicon_url(default: detectkit brand avatar) - Bot avatar image URLicon_emoji(optional) - Emoji icon, used instead of an avatar imagechannel- Override webhook’s default channeltimeout(default:10) - HTTP request timeout
Slack Channel
Section titled “Slack Channel”alert_channels: slack_ops: type: slack webhook_url: "https://hooks.slack.com/services/xxx" channel: "#alerts" # Explicit channel # Bot name + avatar default to the detectkit brand; override with # username / icon_url / icon_emoji.Same fields as Mattermost (Slack-compatible webhook API).
Telegram Channel
Section titled “Telegram Channel”alert_channels: telegram_alerts: type: telegram bot_token: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" chat_id: "-1001234567890" parse_mode: "HTML" # "Markdown", "HTML", or null (default: "HTML") disable_notification: false # Send silently without notification (default: false)Required fields:
type: Must be"telegram"bot_token: Telegram bot API tokenchat_id: Target chat/channel ID
Optional fields:
parse_mode(default:"HTML") - Message formatting:"Markdown","HTML", ornulldisable_notification(default:false) - Send the message silently, without a notification sound
The default
parse_modeis nowHTML(wasMarkdown). The built-in message is HTML-escaped, which fixes a “can’t parse entities” error the old Markdown default raised on params JSON containing underscores (e.g.window_size). Custom templates are sent verbatim under the parse mode, so keep them HTML-safe — or setparse_mode: Markdownto restore the previous behavior.
Email Channel
Section titled “Email Channel”alert_channels: email_ops: type: email smtp_host: "smtp.gmail.com" smtp_port: 587 smtp_username: "your_email@gmail.com" smtp_password: "your_app_password" from_email: "alerts@example.com" to_emails: - "ops@example.com" - "devops@example.com" use_tls: trueRequired fields:
type: Must be"email"smtp_host: SMTP server hostnamesmtp_port: SMTP server portfrom_email: Sender email addressto_emails: List of recipient email addresses
Optional fields:
from_name(default:"detectkit") - Sender display name in theFromheader (the brand logo is also rendered in the HTML body)smtp_username: SMTP authentication username (the channel only logs in when bothsmtp_usernameandsmtp_passwordare set)smtp_password: SMTP authentication passworduse_tls(default:true) - Use TLS encryptionsubject_template(default:"🔴 Alert: {metric_name}") - Email subject, supports{metric_name}template: Custom message body template (falls back to the built-in default)
Discord Channel
Section titled “Discord Channel”alert_channels: discord_ops: type: discord webhook_url: "${DISCORD_WEBHOOK}" # Bot identity is optional — defaults to the detectkit brand name + avatar. # username: "detectkit" # avatar_url: "https://.../bot.png"Required fields:
type: Must be"discord"webhook_url: Discord incoming-webhook URL (https://discord.com/api/webhooks/<id>/<token>)
Optional fields:
username(default:"detectkit") - Bot display nameavatar_url(default: detectkit brand avatar) - Bot avatar image URLtimeout(default:10) - HTTP request timeout
A bare
@nameinmentions:doesn’t ping on Discord — use the literal<@user_id>/<@&role_id>form for a real ping. See the Channels guide → Discord for the full rendering and mention notes.
Microsoft Teams Channel
Section titled “Microsoft Teams Channel”alert_channels: teams_ops: type: teams webhook_url: "${TEAMS_WEBHOOK_URL}"Required fields:
type: Must be"teams"webhook_url: the Workflows app’s webhook-trigger URL (not the retired Office 365 connector)
Optional fields:
timeout(default:10) - HTTP request timeout
The message posts under the Workflow’s own identity — there is no
username/avatar override, and@mentionsrender as plain text without actually pinging. See the Channels guide → Teams for the full caveats.
Google Chat Channel
Section titled “Google Chat Channel”alert_channels: googlechat_ops: type: googlechat webhook_url: "${GOOGLE_CHAT_WEBHOOK_URL}" # icon_url: "https://.../bot.png" # optional — defaults to the detectkit brand avatarRequired fields:
type: Must be"googlechat"webhook_url: the space’s full incoming-webhook URL
Optional fields:
icon_url(default: detectkit brand avatar) - header avatar image URLtimeout(default:10) - HTTP request timeout
Only the space-wide
<users/all>token actually pings; anything else inmentions:renders as a plain, non-pinging@name. See the Channels guide → Google Chat.
ntfy Channel
Section titled “ntfy Channel”alert_channels: ntfy_ops: type: ntfy topic: "my-alerts" # server: "https://ntfy.sh" # default; self-hosted servers work the same way # token: "${NTFY_TOKEN}" # access token -> Authorization: Bearer # priority: 5 # overrides the anomaly/error priority onlyRequired fields:
type: Must be"ntfy"topic: ntfy topic to publish to
Optional fields:
server(default:"https://ntfy.sh") - ntfy server base URLtoken(optional) - ntfy access token (Authorization: Bearer <token>); wins overuser/passworduser/password(optional) - HTTP basic auth, used only whentokenis unsetpriority(optional,1-5) - overrides the anomaly/error notification priority only; recovery/no-data always stay calm at3timeout(default:10) - HTTP request timeout
ntfy has no bot avatar/color-bar concept — see the Channels guide → ntfy for the tag-emoji title, priority mapping and message-size cap.
Generic Webhook Channel
Section titled “Generic Webhook Channel”Sends alerts to any endpoint that accepts a JSON payload (Mattermost/Slack attachments format). Use this for custom webhook receivers or when you need extra HTTP headers (e.g., bearer auth).
alert_channels: custom_hook: type: webhook webhook_url: "https://custom.example.com/webhook" format: attachments # attachments (default) | json | alertmanager secret: "{{ env_var('WEBHOOK_SECRET') }}" # optional HMAC signing secret channel: "#alerts" # Target channel (optional, Slack/Mattermost) timeout: 10 # Request timeout in seconds (default: 10) extra_headers: # Additional HTTP headers (optional) Authorization: "Bearer token" # Bot name + avatar default to the detectkit brand; override with # username / icon_url / icon_emoji.Required fields:
type: Must be"webhook"webhook_url: Endpoint URL to POST the JSON payload to
Optional fields:
format(default:"attachments") - Payload shape:attachments(today’s Mattermost/Slack-compatible payload),json(a flat, stable machine-readable payload), oralertmanager(the Prometheus Alertmanager webhook-receiver payload) —json/alertmanageralso ignore a customtemplate.type: slack/type: mattermostalways sendattachmentsregardless of this fieldsecret(optional) - HMAC signing secret (env-interpolatable); when set, every request carries anX-Detectkit-Signature-256header, whatever theformatusername(default:"detectkit") - Bot display nameicon_url(default: detectkit brand avatar) - Bot avatar image URLicon_emoji(optional) - Emoji icon, used instead of an avatar imagechannel- Override the receiver’s default channeltimeout(default:10) - HTTP request timeoutextra_headers: Dict of additional HTTP headers to send
See the Channels guide → Generic Webhook for the full payload examples of each format and the HMAC verification snippet, and → Rocket.Chat for the recipe (and caveats) to route through this same channel type into a Rocket.Chat incoming webhook.