New to Kestra?
Use blueprints to kickstart your first workflows.
Sync multiple databases into DataHub with one Kestra flow. ForEach fans out ingestion recipes across Postgres and MySQL sources with a Discord summary.
One database in the catalog is a demo; a catalog earns trust when it covers everything. The naive way to get there is one flow per source, which means every improvement gets copy-pasted N times. This blueprint keeps a single flow: io.kestra.plugin.core.flow.ForEach iterates over a list of JSON source definitions, and each iteration renders one io.kestra.plugin.datahub.Ingestion recipe from its JSON with fromJson, a Postgres CRM database and a MySQL warehouse out of the box. Adding a third source is adding one line to the values list. Discord gets a single summary when the whole sync completes.
sync_each_source (io.kestra.plugin.core.flow.ForEach) iterates over the values list. Each value is a JSON string describing one source, its DataHub source type, connection details, and the name of the Kestra secret holding its password.ingest_source builds the recipe dynamically. {{ fromJson(taskrun.value).type }} selects the source type and the config fields fill in from the same JSON, which works because postgres and mysql sources share the same core config keys.{{ secret(fromJson(taskrun.value).password_secret) }} resolves it at runtime, so the flow stays safe to commit with real hostnames in it.datahub-rest sink, resolved from the shared GMS secrets, so all sources land in one catalog.notify sends one Discord summary after the fan-out completes, the errors block sends one alert naming the execution when any source fails, and a disabled-by-default Schedule trigger runs the whole sync nightly at 04:30.fromJson, no recipe files to maintain per source.datahub ingest into one observable pipeline.Fan-out is where orchestration pays for itself. Kestra runs the iterations as separate task runs with separate logs, so one unreachable database fails visibly without hiding the others, and the execution view shows per-source status at a glance. The recipe stays the standard DataHub YAML; only the values that differ per source are parameterized. When the API team hands you the next database, coverage is a pull request that adds one JSON line.
acryldata/datahub-ingestion container image.DATAHUB_GMS_URL: URL of the DataHub GMS server.DATAHUB_TOKEN: DataHub personal access token with permission to ingest metadata.CRM_DB_PASSWORD: password for the Postgres CRM reader user.WAREHOUSE_DB_PASSWORD: password for the MySQL warehouse reader user.DISCORD_WEBHOOK_URL: Discord incoming webhook URL.values to point at real databases and execute the flow.disabled: false on the nightly trigger.values; any DataHub source sharing these config keys works as-is.concurrencyLimit on the ForEach to sync large fleets a few sources at a time.schema_pattern fields to the JSON definitions and the recipe to scope noisy databases.JSON flow input or a KV store entry when the list should change without editing the flow.