New to Kestra?
Use blueprints to kickstart your first workflows.
Replicate SQL tables incrementally with dlt on Kestra. An updated-at cursor moves only changed rows, merge keeps the destination clean, Slack gets the delta.
Stop re-copying whole tables on every sync. This blueprint uses dlt's sql_table source with an incremental cursor: each run reads only rows whose updated_at advanced past the last stored cursor position, merges them into the destination on the table's primary key, and reports the delta size to Slack. The cursor state lives in the destination itself, dlt's _dlt_pipeline_state table, so the flow is stateless from Kestra's point of view: reruns, retries, and even a fresh Kestra instance resume from the right position.
sync_table (io.kestra.plugin.dlt.Run) installs the sql-database extra plus common drivers in beforeCommands, then builds a sql_table source from the SOURCE_DB_URL secret passed as an environment variable.dlt.sources.incremental("updated_at") tells dlt to track the highest cursor value seen and to filter the next extraction with it, server-side, as a WHERE clause.write_disposition: merge deduplicates on the table's primary key, so a row updated twice between runs lands once, with the latest values.notify posts it to Slack, and the errors block explains that a failed run never advances the cursor, so nothing is skipped.Schedule covers business days; the flow's table_name and cursor_column inputs make one flow serve many tables.Incremental sync is a scheduling problem as much as a data problem: something must run the pipeline on cadence, retry transient database failures, parameterize it per table, and alert when the sync stalls. Kestra adds all of that around dlt's unchanged incremental logic, with per-table executions in the history showing exactly how many rows each run moved and when.
warehouse_path input); the incremental cursor is stored there.SOURCE_DB_URL: SQLAlchemy connection URL, e.g. postgresql://user:pass@host:5432/db.SLACK_WEBHOOK_URL: Slack incoming webhook URL.disabled: false on the hourly trigger.ForEach over a table list, or use dlt's sql_database() source to reflect a whole schema at once.bigquery, snowflake, or postgres with credentials in task env.