This is an automated email from the ASF dual-hosted git repository.
zhangshenghang pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/seatunnel.git
The following commit(s) were added to refs/heads/dev by this push:
new b5fd7601bd [Docs] Add first-job entry and getting started recipes
(#11058)
b5fd7601bd is described below
commit b5fd7601bd577b2ff2e482a920943258b43694d4
Author: David Zollo <[email protected]>
AuthorDate: Sun Jun 21 13:53:50 2026 +0800
[Docs] Add first-job entry and getting started recipes (#11058)
Co-authored-by: Daniel <[email protected]>
---
.../locally/quick-start-seatunnel-engine.md | 3 +-
.../getting-started/locally/run-your-first-job.md | 99 ++++++++++
.../getting-started/recipes/file-to-starrocks.md | 145 +++++++++++++++
docs/en/getting-started/recipes/http-to-jdbc.md | 126 +++++++++++++
docs/en/getting-started/recipes/jdbc-to-s3.md | 137 ++++++++++++++
.../en/getting-started/recipes/kafka-to-iceberg.md | 138 ++++++++++++++
docs/en/getting-started/recipes/multi-table-cdc.md | 204 +++++++++++++++++++++
.../getting-started/recipes/mysql-cdc-to-doris.md | 177 ++++++++++++++++++
docs/en/introduction/about.md | 26 ++-
docs/sidebars.js | 29 +++
.../locally/quick-start-seatunnel-engine.md | 3 +-
.../getting-started/locally/run-your-first-job.md | 99 ++++++++++
.../getting-started/recipes/file-to-starrocks.md | 145 +++++++++++++++
docs/zh/getting-started/recipes/http-to-jdbc.md | 126 +++++++++++++
docs/zh/getting-started/recipes/jdbc-to-s3.md | 137 ++++++++++++++
.../zh/getting-started/recipes/kafka-to-iceberg.md | 138 ++++++++++++++
docs/zh/getting-started/recipes/multi-table-cdc.md | 204 +++++++++++++++++++++
.../getting-started/recipes/mysql-cdc-to-doris.md | 177 ++++++++++++++++++
docs/zh/introduction/about.md | 26 ++-
19 files changed, 2135 insertions(+), 4 deletions(-)
diff --git a/docs/en/getting-started/locally/quick-start-seatunnel-engine.md
b/docs/en/getting-started/locally/quick-start-seatunnel-engine.md
index 54adbaf3a6..6c4eff10e9 100644
--- a/docs/en/getting-started/locally/quick-start-seatunnel-engine.md
+++ b/docs/en/getting-started/locally/quick-start-seatunnel-engine.md
@@ -109,7 +109,7 @@ is a sign to determine whether the command ran successfully
or not.
The SeaTunnel console will print some logs as below:
```shell
-2022-12-19 11:01:45,417 INFO
org.apache.seatunnel.connectors.seatunnel.console.sink.ConsoleSinkWriter -
output rowType: name<STRING>, age<INT>
+2022-12-19 11:01:45,417 INFO
org.apache.seatunnel.connectors.seatunnel.console.sink.ConsoleSinkWriter -
output rowType: new_name<STRING>, age<INT>
2022-12-19 11:01:46,489 INFO
org.apache.seatunnel.connectors.seatunnel.console.sink.ConsoleSinkWriter -
subtaskIndex=0 rowIndex=1: SeaTunnelRow#tableId=-1 SeaTunnelRow#kind=INSERT:
CpiOd, 8520946
2022-12-19 11:01:46,490 INFO
org.apache.seatunnel.connectors.seatunnel.console.sink.ConsoleSinkWriter -
subtaskIndex=0 rowIndex=2: SeaTunnelRow#tableId=-1 SeaTunnelRow#kind=INSERT:
eQqTs, 1256802974
2022-12-19 11:01:46,490 INFO
org.apache.seatunnel.connectors.seatunnel.console.sink.ConsoleSinkWriter -
subtaskIndex=0 rowIndex=3: SeaTunnelRow#tableId=-1 SeaTunnelRow#kind=INSERT:
UsRgO, 2053193072
@@ -245,6 +245,7 @@ Recommendation:
- Start with [Getting Started Overview](../overview.md) if you want a guided
reading path across deployment, quick start, and configuration.
- Use [Job Configuration Guide](../job-configuration-guide.md) when you are
ready to replace the sample source and sink with real connectors.
+- If you want runnable source-to-sink examples next, continue with [MySQL CDC
to Doris](../recipes/mysql-cdc-to-doris.md), [JDBC to
S3](../recipes/jdbc-to-s3.md), [Kafka to
Iceberg](../recipes/kafka-to-iceberg.md), [Http to
JDBC](../recipes/http-to-jdbc.md), [File to
StarRocks](../recipes/file-to-starrocks.md), or [Multi-table
CDC](../recipes/multi-table-cdc.md).
- Start writing your own config file, choose the
[connector](../../connectors/source) you want to use, and configure the
parameters according to the connector documentation.
- If you want to deploy a multi-node SeaTunnel Engine cluster, continue with
[SeaTunnel Engine(Zeta) Deployment](../../engines/zeta/deployment.md).
- See [SeaTunnel Engine(Zeta)](../../engines/zeta/about.md) if you want to
learn more about SeaTunnel Engine.
diff --git a/docs/en/getting-started/locally/run-your-first-job.md
b/docs/en/getting-started/locally/run-your-first-job.md
new file mode 100644
index 0000000000..9bde8145bf
--- /dev/null
+++ b/docs/en/getting-started/locally/run-your-first-job.md
@@ -0,0 +1,99 @@
+---
+sidebar_position: 1
+title: Run your first job
+---
+
+# Run your first job
+
+This page gives you the shortest path to a successful SeaTunnel run. The
example stays fully local, does not require MySQL, Kafka, or object storage,
and helps you confirm that your installation, config parsing, and execution
engine are all working.
+
+## Step 1: Finish local deployment
+
+Complete [Deployment](deployment.md) first and make sure `bin/seatunnel.sh` is
available in your SeaTunnel home directory.
+
+## Step 2: Install only the plugins this sample needs
+
+Follow [Deployment > Download The Connector
Plugins](deployment.md#download-the-connector-plugins), then keep only
`connector-fake` and `connector-console` in `config/plugin_config`:
+
+```plugin_config
+--seatunnel-connectors--
+connector-fake
+connector-console
+--end--
+```
+
+Install the plugins and confirm they were downloaded into
`${SEATUNNEL_HOME}/connectors`:
+
+```bash
+cd "${SEATUNNEL_HOME}"
+sh bin/install-plugin.sh
+ls connectors | rg 'connector-(fake|console)'
+```
+
+## Step 3: Use a minimal job
+
+Save the following config as `config/v2.batch.config.template` or another
local file:
+
+```hocon
+env {
+ parallelism = 1
+ job.mode = "BATCH"
+}
+
+source {
+ FakeSource {
+ plugin_output = "fake"
+ row.num = 16
+ schema = {
+ fields {
+ name = "string"
+ age = "int"
+ }
+ }
+ }
+}
+
+transform {
+ FieldMapper {
+ plugin_input = "fake"
+ plugin_output = "fake1"
+ field_mapper = {
+ age = age
+ name = new_name
+ }
+ }
+}
+
+sink {
+ Console {
+ plugin_input = "fake1"
+ }
+}
+```
+
+## Step 4: Run it in local mode
+
+```shell
+cd "apache-seatunnel-${version}"
+./bin/seatunnel.sh --config ./config/v2.batch.config.template -m local
+```
+
+## Expected validation result
+
+- The process starts successfully without connector loading errors.
+- The console prints an `output rowType` line for the mapped fields.
+- The console prints 16 rows from `ConsoleSinkWriter`.
+- The batch job exits successfully after all rows are written.
+
+If this works, your basic local path is healthy and you can move on to real
pipelines.
+
+## Next step
+
+- For the full local walkthrough, continue with [Quick Start With SeaTunnel
Engine](quick-start-seatunnel-engine.md).
+- For runnable source-to-sink examples, start with these recipes:
+ - [MySQL CDC to Doris](../recipes/mysql-cdc-to-doris.md)
+ - [JDBC to S3](../recipes/jdbc-to-s3.md)
+ - [Kafka to Iceberg](../recipes/kafka-to-iceberg.md)
+ - [Http to JDBC](../recipes/http-to-jdbc.md)
+ - [File to StarRocks](../recipes/file-to-starrocks.md)
+ - [Multi-table CDC](../recipes/multi-table-cdc.md)
diff --git a/docs/en/getting-started/recipes/file-to-starrocks.md
b/docs/en/getting-started/recipes/file-to-starrocks.md
new file mode 100644
index 0000000000..dfa08d8fcf
--- /dev/null
+++ b/docs/en/getting-started/recipes/file-to-starrocks.md
@@ -0,0 +1,145 @@
+---
+sidebar_position: 5
+title: File to StarRocks
+---
+
+# File to StarRocks
+
+Use this recipe when you want to import local CSV or text files into StarRocks
for fast analytical queries.
+
+## Prerequisites
+
+1. Finish [Run your first job](../locally/run-your-first-job.md).
+
+2. Install the plugins required by this recipe. Follow [Deployment > Download
The Connector
Plugins](../locally/deployment.md#download-the-connector-plugins), then keep
only the plugins below in `config/plugin_config`:
+
+```plugin_config
+--seatunnel-connectors--
+connector-file-local
+connector-starrocks
+--end--
+```
+
+```bash
+cd "${SEATUNNEL_HOME}"
+sh bin/install-plugin.sh
+ls connectors | rg 'connector-(file-local|starrocks)'
+```
+
+3. Put the MySQL JDBC driver required by the StarRocks sink into
`${SEATUNNEL_HOME}/lib`, then confirm the jar is visible:
+
+```bash
+ls "${SEATUNNEL_HOME}/lib" | rg 'mysql-connector'
+```
+
+4. Prepare the local input file and make sure the SeaTunnel process can read
it:
+
+```bash
+mkdir -p /tmp/seatunnel/input
+cat <<'EOF' > /tmp/seatunnel/input/customers.csv
+id,name,city,updated_at
+1001,Alice,Shanghai,2026-06-12 10:00:00
+1002,Bob,Beijing,2026-06-12 10:05:00
+1003,Carol,Hangzhou,2026-06-12 10:10:00
+EOF
+```
+
+5. Create the target database and table in StarRocks before running the job.
+
+## Minimal configuration
+
+This example reads a local CSV file with a header line and writes the rows to
an existing StarRocks primary-key table.
+
+Prepare the target table first:
+
+```sql
+CREATE DATABASE IF NOT EXISTS sync_demo;
+
+CREATE TABLE IF NOT EXISTS sync_demo.customers (
+ id BIGINT NOT NULL,
+ name STRING,
+ city STRING,
+ updated_at DATETIME
+)
+ENGINE=OLAP
+PRIMARY KEY(id)
+DISTRIBUTED BY HASH(id)
+PROPERTIES (
+ "replication_num" = "1"
+);
+```
+
+```hocon
+env {
+ parallelism = 1
+ job.mode = "BATCH"
+}
+
+source {
+ LocalFile {
+ plugin_output = "customers_file"
+ path = "/tmp/seatunnel/input/customers.csv"
+ file_format_type = "csv"
+ csv_use_header_line = true
+ schema = {
+ fields {
+ id = bigint
+ name = string
+ city = string
+ updated_at = timestamp
+ }
+ }
+ }
+}
+
+sink {
+ StarRocks {
+ plugin_input = "customers_file"
+ nodeUrls = ["starrocks-fe:8030"]
+ base-url = "jdbc:mysql://starrocks-fe:9030/sync_demo"
+ username = "root"
+ password = ""
+ database = "sync_demo"
+ table = "customers"
+ batch_max_rows = 1000
+ schema_save_mode = "IGNORE"
+ starrocks.config = {
+ format = "JSON"
+ strip_outer_array = true
+ }
+ }
+}
+```
+
+## Run the job
+
+Save the config as `config/file-to-starrocks.conf`, then run SeaTunnel in
local mode:
+
+```bash
+cd "${SEATUNNEL_HOME}"
+./bin/seatunnel.sh --config ./config/file-to-starrocks.conf -m local
+```
+
+## Validation result
+
+1. Run the job and confirm it finishes without StarRocks stream load errors.
+2. Check the target table in StarRocks.
+
+```sql
+SELECT COUNT(*) FROM sync_demo.customers;
+SELECT id, name, city, updated_at FROM sync_demo.customers ORDER BY id;
+```
+
+If the imported rows in StarRocks match the file content, the pipeline is
working.
+
+## Common pitfalls
+
+- `base-url` is missing even though `nodeUrls` is configured.
+- The file has a header row, but `csv_use_header_line = true` is not set.
+- The source schema does not match the file delimiter or timestamp format.
+- The target table was not created before the job. This recipe uses
`schema_save_mode = "IGNORE"` because the local file source does not provide
primary-key metadata for StarRocks auto DDL.
+
+## Related docs
+
+- [LocalFile source](../../connectors/source/LocalFile.md)
+- [StarRocks sink](../../connectors/sink/StarRocks.md)
diff --git a/docs/en/getting-started/recipes/http-to-jdbc.md
b/docs/en/getting-started/recipes/http-to-jdbc.md
new file mode 100644
index 0000000000..45bfb660be
--- /dev/null
+++ b/docs/en/getting-started/recipes/http-to-jdbc.md
@@ -0,0 +1,126 @@
+---
+sidebar_position: 4
+title: Http to JDBC
+---
+
+# Http to JDBC
+
+Use this recipe when you want to pull structured data from an HTTP API and
store the result in a relational database.
+
+## Prerequisites
+
+1. Finish [Run your first job](../locally/run-your-first-job.md).
+
+2. Install the plugins required by this recipe. Follow [Deployment > Download
The Connector
Plugins](../locally/deployment.md#download-the-connector-plugins), then keep
only the plugins below in `config/plugin_config`:
+
+```plugin_config
+--seatunnel-connectors--
+connector-http-base
+connector-jdbc
+--end--
+```
+
+```bash
+cd "${SEATUNNEL_HOME}"
+sh bin/install-plugin.sh
+ls connectors | rg 'connector-(http-base|jdbc)'
+```
+
+3. Put the target database JDBC driver into `${SEATUNNEL_HOME}/lib`, then
confirm the jar is visible:
+
+```bash
+ls "${SEATUNNEL_HOME}/lib" | rg 'postgresql'
+```
+
+4. Inspect the HTTP response before running the job. The sample endpoint from
the [Http source](../../connectors/source/Http.md) should return a JSON body
that contains top-level fields such as `c_string` and `c_int`:
+
+```bash
+curl http://mockserver:1080/example/http
+```
+
+If your real API nests the useful records under another field, define
`json_field` or `content_field` before you continue.
+
+5. Prepare the PostgreSQL target database and grant the sink user permission
to create tables in `public`, because this recipe uses `generate_sink_sql =
true`:
+
+```sql
+CREATE USER test WITH PASSWORD 'test';
+CREATE DATABASE test OWNER test;
+```
+
+Reconnect to database `test`, then run:
+
+```sql
+GRANT USAGE, CREATE ON SCHEMA public TO test;
+```
+
+## Minimal configuration
+
+```hocon
+env {
+ parallelism = 1
+ job.mode = "BATCH"
+}
+
+source {
+ Http {
+ plugin_output = "http_orders"
+ url = "http://mockserver:1080/example/http"
+ method = "GET"
+ format = "json"
+ schema = {
+ fields {
+ c_string = string
+ c_int = int
+ }
+ }
+ }
+}
+
+sink {
+ Jdbc {
+ plugin_input = "http_orders"
+ driver = "org.postgresql.Driver"
+ url = "jdbc:postgresql://postgresql:5432/test?loggerLevel=OFF"
+ username = "test"
+ password = "test"
+ generate_sink_sql = true
+ database = "test"
+ table = "public.http_orders"
+ primary_keys = ["c_string"]
+ batch_size = 100
+ }
+}
+```
+
+## Run the job
+
+Save the config as `config/http-to-jdbc.conf`, then run SeaTunnel in local
mode:
+
+```bash
+cd "${SEATUNNEL_HOME}"
+./bin/seatunnel.sh --config ./config/http-to-jdbc.conf -m local
+```
+
+## Validation result
+
+1. Run the job and confirm there are no HTTP parse or JDBC DDL errors.
+2. Query the target table and compare the row count with the API response.
+
+```sql
+SELECT COUNT(*) FROM public.http_orders;
+SELECT c_string, c_int FROM public.http_orders ORDER BY c_string;
+```
+
+If the rows in the target table match the HTTP response, the pipeline is
working. With the default mock response, you should see the same `c_string` and
`c_int` values you saw in `curl`.
+
+## Common pitfalls
+
+- The response body is JSON, but the configured schema does not match the
actual field names or types.
+- The API data is nested, but `content_field` or `json_field` is not
configured.
+- Pagination or rate limits exist on the source API, but the job treats it as
a single-page endpoint.
+- The JDBC sink auto-creates a table, but the chosen primary key does not
uniquely identify records.
+
+## Related docs
+
+- [Http source](../../connectors/source/Http.md)
+- [JDBC sink](../../connectors/sink/Jdbc.md)
diff --git a/docs/en/getting-started/recipes/jdbc-to-s3.md
b/docs/en/getting-started/recipes/jdbc-to-s3.md
new file mode 100644
index 0000000000..161b2c51c5
--- /dev/null
+++ b/docs/en/getting-started/recipes/jdbc-to-s3.md
@@ -0,0 +1,137 @@
+---
+sidebar_position: 2
+title: JDBC to S3
+---
+
+# JDBC to S3
+
+Use this recipe when you want to export table data from a relational database
to an S3-compatible object store.
+
+## Prerequisites
+
+1. Finish [Run your first job](../locally/run-your-first-job.md).
+
+2. Install the plugins required by this recipe. Follow [Deployment > Download
The Connector
Plugins](../locally/deployment.md#download-the-connector-plugins), then keep
only the plugins below in `config/plugin_config`:
+
+```plugin_config
+--seatunnel-connectors--
+connector-jdbc
+connector-file-s3
+--end--
+```
+
+```bash
+cd "${SEATUNNEL_HOME}"
+sh bin/install-plugin.sh
+ls connectors | rg 'connector-(jdbc|file-s3)'
+```
+
+3. Put the source database JDBC driver into `${SEATUNNEL_HOME}/lib` for
SeaTunnel Zeta, then confirm the jar is visible:
+
+```bash
+ls "${SEATUNNEL_HOME}/lib" | rg 'mysql-connector'
+```
+
+4. Put `hadoop-aws` and the AWS SDK bundle required by the S3 connector into
`${SEATUNNEL_HOME}/lib`, then confirm both jars are present:
+
+```bash
+ls "${SEATUNNEL_HOME}/lib" | rg 'hadoop-aws|aws-java-sdk-bundle'
+```
+
+5. Prepare the source table in the relational database. This example uses
MySQL and exports two rows from `analytics.orders`:
+
+```sql
+CREATE DATABASE IF NOT EXISTS analytics;
+
+CREATE TABLE IF NOT EXISTS analytics.orders (
+ id BIGINT PRIMARY KEY,
+ customer_id BIGINT,
+ total_amount DECIMAL(16, 2),
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+
+INSERT INTO analytics.orders (id, customer_id, total_amount, updated_at) VALUES
+ (5001, 101, 19.99, NOW()),
+ (5002, 102, 29.99, NOW());
+```
+
+6. Prepare an S3 bucket and credentials with write permission. The example
below assumes the bucket already exists and that `access_key` and `secret_key`
can write to `s3://company-data-lake/seatunnel/orders/`.
+
+## Minimal configuration
+
+This example exports a query result from MySQL to S3 in JSON lines format so
that the output is easy to inspect.
+
+```hocon
+env {
+ parallelism = 1
+ job.mode = "BATCH"
+}
+
+source {
+ Jdbc {
+ plugin_output = "orders_jdbc"
+ url = "jdbc:mysql://mysql:3306/analytics"
+ driver = "com.mysql.cj.jdbc.Driver"
+ username = "root"
+ password = "password"
+ query = "select id, customer_id, total_amount, updated_at from orders"
+ }
+}
+
+sink {
+ S3File {
+ plugin_input = "orders_jdbc"
+ bucket = "s3a://company-data-lake"
+ path = "/seatunnel/orders/"
+ fs.s3a.endpoint = "s3.us-east-1.amazonaws.com"
+ fs.s3a.aws.credentials.provider =
"org.apache.hadoop.fs.s3a.SimpleAWSCredentialsProvider"
+ access_key = "your-access-key"
+ secret_key = "your-secret-key"
+ file_format_type = "json"
+ row_delimiter = "\n"
+ custom_filename = true
+ file_name_expression = "orders"
+ filename_extension = "json"
+ single_file_mode = true
+ is_enable_transaction = false
+ schema_save_mode = "CREATE_SCHEMA_WHEN_NOT_EXIST"
+ data_save_mode = "APPEND_DATA"
+ }
+}
+```
+
+## Run the job
+
+Save the config as `config/jdbc-to-s3.conf`, then run SeaTunnel in local mode:
+
+```bash
+cd "${SEATUNNEL_HOME}"
+./bin/seatunnel.sh --config ./config/jdbc-to-s3.conf -m local
+```
+
+## Validation result
+
+1. Run the source query directly in the database and record the expected row
count.
+2. Start the SeaTunnel job.
+3. Verify that new objects are written to the expected S3 prefix.
+
+```bash
+aws s3 ls s3://company-data-lake/seatunnel/orders/ --recursive
+aws s3 cp s3://company-data-lake/seatunnel/orders/orders.json - | head
+```
+
+If objects are created under the target prefix and the exported content
matches the source query, the pipeline is working.
+
+## Common pitfalls
+
+- The JDBC driver is available on your workstation but not under
`${SEATUNNEL_HOME}/lib`.
+- `bucket` and `path` are mixed up. Keep the bucket in `bucket` and the prefix
in `path`.
+- The credential provider does not match the authentication method you
configured.
+- Large tables are exported through one unbounded query without filtering or
partitioning.
+- Fixed filenames are only safe for this single-file tutorial. If you enable
transactions again, keep `${transactionId}` in `file_name_expression`.
+- The target endpoint is S3-compatible, but the `fs.s3a.endpoint` value still
points to AWS.
+
+## Related docs
+
+- [JDBC source](../../connectors/source/Jdbc.md)
+- [S3File sink](../../connectors/sink/S3File.md)
diff --git a/docs/en/getting-started/recipes/kafka-to-iceberg.md
b/docs/en/getting-started/recipes/kafka-to-iceberg.md
new file mode 100644
index 0000000000..ab50c86758
--- /dev/null
+++ b/docs/en/getting-started/recipes/kafka-to-iceberg.md
@@ -0,0 +1,138 @@
+---
+sidebar_position: 3
+title: Kafka to Iceberg
+---
+
+# Kafka to Iceberg
+
+Use this recipe when you want to land streaming events from Kafka into an
Iceberg table for downstream analytics.
+
+## Prerequisites
+
+1. Finish [Run your first job](../locally/run-your-first-job.md).
+
+2. Install the plugins required by this recipe. Follow [Deployment > Download
The Connector
Plugins](../locally/deployment.md#download-the-connector-plugins), then keep
only the plugins below in `config/plugin_config`:
+
+```plugin_config
+--seatunnel-connectors--
+connector-kafka
+connector-iceberg
+--end--
+```
+
+```bash
+cd "${SEATUNNEL_HOME}"
+sh bin/install-plugin.sh
+ls connectors | rg 'connector-(kafka|iceberg)'
+```
+
+3. If you use Flink or Spark, add the Iceberg dependencies required by your
environment, including `hive-exec` and `libfb303` when needed.
+
+4. Choose an empty, writable Iceberg warehouse path. This tutorial uses a
local Hadoop catalog at `file:///tmp/seatunnel/iceberg/warehouse-demo`:
+
+```bash
+mkdir -p /tmp/seatunnel/iceberg/warehouse-demo
+```
+
+5. Create the Kafka topic and produce a few JSON messages before starting the
job:
+
+```bash
+kafka-topics.sh \
+ --create \
+ --if-not-exists \
+ --topic orders \
+ --bootstrap-server kafka:9092 \
+ --partitions 1 \
+ --replication-factor 1
+
+kafka-console-producer.sh --topic orders --bootstrap-server kafka:9092 <<'EOF'
+{"id":1001,"customer_id":2001,"total_amount":19.99,"event_date":"2026-06-12"}
+{"id":1002,"customer_id":2002,"total_amount":29.99,"event_date":"2026-06-12"}
+EOF
+```
+
+## Minimal configuration
+
+```hocon
+env {
+ parallelism = 1
+ job.mode = "STREAMING"
+ checkpoint.interval = 5000
+}
+
+source {
+ Kafka {
+ plugin_output = "orders_kafka"
+ topic = "orders"
+ bootstrap.servers = "kafka:9092"
+ consumer.group = "seatunnel-orders"
+ start_mode = "earliest"
+ format = "json"
+ schema = {
+ fields {
+ id = bigint
+ customer_id = bigint
+ total_amount = "decimal(16, 2)"
+ event_date = string
+ }
+ }
+ }
+}
+
+sink {
+ Iceberg {
+ plugin_input = "orders_kafka"
+ catalog_name = "seatunnel_demo"
+ namespace = "lakehouse"
+ table = "orders"
+ iceberg.catalog.config = {
+ type = "hadoop"
+ warehouse = "file:///tmp/seatunnel/iceberg/warehouse-demo"
+ }
+ iceberg.table.primary-keys = "id"
+ iceberg.table.partition-keys = "event_date"
+ iceberg.table.upsert-mode-enabled = true
+ iceberg.table.schema-evolution-enabled = true
+ case_sensitive = true
+ }
+}
+```
+
+## Run the job
+
+Save the config as `config/kafka-to-iceberg.conf`, then start SeaTunnel in
local mode:
+
+```bash
+cd "${SEATUNNEL_HOME}"
+./bin/seatunnel.sh --config ./config/kafka-to-iceberg.conf -m local
+```
+
+Keep the job running while Kafka messages are being consumed, because this is
a streaming pipeline.
+
+## Validation result
+
+1. Verify that Iceberg metadata and data files appear under the warehouse path.
+2. Query the Iceberg table with Spark, Trino, or another Iceberg-compatible
engine.
+
+```bash
+ls /tmp/seatunnel/iceberg/warehouse-demo/lakehouse/orders
+spark-sql \
+ --conf
spark.sql.catalog.seatunnel_demo=org.apache.iceberg.spark.SparkCatalog \
+ --conf spark.sql.catalog.seatunnel_demo.type=hadoop \
+ --conf
spark.sql.catalog.seatunnel_demo.warehouse=file:///tmp/seatunnel/iceberg/warehouse-demo
\
+ -e "SELECT COUNT(*) FROM seatunnel_demo.lakehouse.orders"
+```
+
+If the table can be queried and the row count matches the Kafka messages you
produced, the pipeline is working. With the two sample messages above, the
count should be `2`.
+
+## Common pitfalls
+
+- JSON messages in Kafka do not match the schema defined in the source block.
+- Checkpointing is disabled in a streaming pipeline, which weakens restart and
consistency behavior.
+- The Iceberg catalog type is correct, but the warehouse path is not writable
by the engine process.
+- Upsert mode is enabled even though the incoming records do not have stable
primary keys.
+
+## Related docs
+
+- [Kafka source](../../connectors/source/Kafka.md)
+- [Iceberg sink](../../connectors/sink/Iceberg.md)
diff --git a/docs/en/getting-started/recipes/multi-table-cdc.md
b/docs/en/getting-started/recipes/multi-table-cdc.md
new file mode 100644
index 0000000000..9f49d3521e
--- /dev/null
+++ b/docs/en/getting-started/recipes/multi-table-cdc.md
@@ -0,0 +1,204 @@
+---
+sidebar_position: 6
+title: Multi-table CDC
+---
+
+# Multi-table CDC
+
+Use this recipe when you want one SeaTunnel job to capture changes from
multiple upstream tables and route each table to its own downstream table
automatically.
+
+## Prerequisites
+
+1. Finish [Run your first job](../locally/run-your-first-job.md).
+
+2. Install the plugins required by this recipe. Follow [Deployment > Download
The Connector
Plugins](../locally/deployment.md#download-the-connector-plugins), then keep
only the plugins below in `config/plugin_config`:
+
+```plugin_config
+--seatunnel-connectors--
+connector-cdc-mysql
+connector-jdbc
+--end--
+```
+
+```bash
+cd "${SEATUNNEL_HOME}"
+sh bin/install-plugin.sh
+ls connectors | rg 'connector-(cdc-mysql|jdbc)'
+```
+
+3. If you use SeaTunnel Zeta, place both the MySQL JDBC driver and the
PostgreSQL JDBC driver into `${SEATUNNEL_HOME}/lib`, then confirm they are
visible:
+
+```bash
+ls "${SEATUNNEL_HOME}/lib" | rg 'mysql-connector|postgresql'
+```
+
+4. Prepare the MySQL source tables. Each upstream table should have a stable
primary key because this recipe routes CDC changes to downstream upsert tables
automatically.
+
+```sql
+CREATE DATABASE IF NOT EXISTS inventory;
+
+CREATE TABLE IF NOT EXISTS inventory.orders (
+ id BIGINT PRIMARY KEY,
+ order_status VARCHAR(32),
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+);
+
+CREATE TABLE IF NOT EXISTS inventory.customers (
+ id BIGINT PRIMARY KEY,
+ customer_name VARCHAR(64),
+ city VARCHAR(64),
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+);
+
+CREATE TABLE IF NOT EXISTS inventory.products (
+ id BIGINT PRIMARY KEY,
+ product_name VARCHAR(64),
+ unit_price DECIMAL(10, 2),
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+);
+
+INSERT INTO inventory.orders (id, order_status, updated_at) VALUES
+ (2001, 'CREATED', NOW());
+
+INSERT INTO inventory.customers (id, customer_name, city, updated_at) VALUES
+ (3001, 'Alice', 'Shanghai', NOW());
+
+INSERT INTO inventory.products (id, product_name, unit_price, updated_at)
VALUES
+ (4001, 'Keyboard', 99.00, NOW());
+```
+
+5. Create the MySQL CDC user and grant the required privileges:
+
+```sql
+CREATE USER IF NOT EXISTS 'st_user_source'@'%' IDENTIFIED BY 'mysqlpw';
+GRANT SELECT, RELOAD, SHOW DATABASES, REPLICATION SLAVE, REPLICATION CLIENT
+ON *.* TO 'st_user_source'@'%';
+FLUSH PRIVILEGES;
+```
+
+6. Verify that MySQL binlog is ready:
+
+```sql
+SHOW VARIABLES WHERE variable_name IN ('log_bin', 'binlog_format',
'binlog_row_image');
+```
+
+The expected values are `log_bin = ON`, `binlog_format = ROW`, and
`binlog_row_image = FULL`.
+
+7. Prepare the PostgreSQL target database and grant the sink user permission
to create tables in `public`:
+
+```sql
+CREATE USER st_user_sink WITH PASSWORD 'pgpw';
+CREATE DATABASE sync_demo;
+GRANT ALL PRIVILEGES ON DATABASE sync_demo TO st_user_sink;
+```
+
+Reconnect to `sync_demo`, then run:
+
+```sql
+GRANT USAGE, CREATE ON SCHEMA public TO st_user_sink;
+```
+
+This recipe uses `generate_sink_sql = true`, so SeaTunnel will create tables
such as `public.st_orders` and `public.st_customers` automatically on the first
run.
+
+## Minimal configuration
+
+This example reads multiple MySQL tables through one `table-pattern` and
writes them to PostgreSQL tables named `st_<upstream_table_name>`.
+
+```hocon
+env {
+ parallelism = 1
+ job.mode = "STREAMING"
+ checkpoint.interval = 5000
+}
+
+source {
+ MySQL-CDC {
+ plugin_output = "mysql_multi"
+ startup.mode = "initial"
+ server-id = 5652
+ username = "st_user_source"
+ password = "mysqlpw"
+ database-pattern = "inventory"
+ table-pattern = "inventory\\.(orders|customers|products)"
+ url = "jdbc:mysql://mysql:3306/inventory"
+ }
+}
+
+sink {
+ Jdbc {
+ plugin_input = "mysql_multi"
+ driver = "org.postgresql.Driver"
+ url = "jdbc:postgresql://postgresql:5432/sync_demo"
+ username = "st_user_sink"
+ password = "pgpw"
+ generate_sink_sql = true
+ database = "sync_demo"
+ table = "public.st_${table_name}"
+ primary_keys = ["${primary_key}"]
+ schema_save_mode = "CREATE_SCHEMA_WHEN_NOT_EXIST"
+ data_save_mode = "APPEND_DATA"
+ }
+}
+```
+
+## Run the job
+
+Save the config as `config/multi-table-cdc.conf`, then start SeaTunnel in
local mode:
+
+```bash
+cd "${SEATUNNEL_HOME}"
+./bin/seatunnel.sh --config ./config/multi-table-cdc.conf -m local
+```
+
+Keep the job running while you verify new changes from MySQL, because this is
a streaming CDC pipeline.
+
+## Validation result
+
+1. Start the job and let the initial snapshot finish.
+2. Confirm that SeaTunnel created multiple target tables in PostgreSQL:
+
+```sql
+SELECT table_name
+FROM information_schema.tables
+WHERE table_schema = 'public' AND table_name LIKE 'st_%'
+ORDER BY table_name;
+```
+
+3. Apply changes on each MySQL source table:
+
+```sql
+INSERT INTO inventory.orders (id, order_status, updated_at)
+VALUES (2002, 'PAID', NOW());
+
+UPDATE inventory.customers
+SET city = 'Hangzhou', updated_at = NOW()
+WHERE id = 3001;
+
+INSERT INTO inventory.products (id, product_name, unit_price, updated_at)
+VALUES (4002, 'Mouse', 59.00, NOW());
+```
+
+4. Verify that each downstream table received only its own source data:
+
+```sql
+SELECT id, order_status FROM public.st_orders ORDER BY id;
+SELECT id, customer_name, city FROM public.st_customers ORDER BY id;
+SELECT id, product_name, unit_price FROM public.st_products ORDER BY id;
+```
+
+If each upstream table is routed to its own target table and changes continue
to flow, the multi-table CDC pipeline is working.
+
+## Common pitfalls
+
+- The regular expression in `table-pattern` is not escaped correctly. In
HOCON, `.` usually needs `\\.` when you mean a literal dot.
+- MySQL binlog or CDC user privileges are incomplete, so the job can read the
snapshot but cannot continue reading incremental changes.
+- Placeholder-based sink routing is not configured, so multiple source tables
are written into one target table accidentally.
+- The PostgreSQL sink user can connect to the database but does not have
`CREATE` permission on schema `public`.
+- Upstream tables do not have primary keys, but the sink is configured as if
upsert semantics were available.
+- The downstream naming convention is valid for one database but invalid for
another because schema and table placeholders are used differently.
+
+## Related docs
+
+- [MySQL CDC source](../../connectors/source/MySQL-CDC.md)
+- [JDBC sink](../../connectors/sink/Jdbc.md)
+- [Multi-table synchronization
architecture](../../architecture/features/multi-table.md)
diff --git a/docs/en/getting-started/recipes/mysql-cdc-to-doris.md
b/docs/en/getting-started/recipes/mysql-cdc-to-doris.md
new file mode 100644
index 0000000000..e5e2fab1b9
--- /dev/null
+++ b/docs/en/getting-started/recipes/mysql-cdc-to-doris.md
@@ -0,0 +1,177 @@
+---
+sidebar_position: 1
+title: MySQL CDC to Doris
+---
+
+# MySQL CDC to Doris
+
+Use this recipe when you want to capture row-level changes from MySQL and keep
a Doris table updated continuously.
+
+## Prerequisites
+
+1. Finish [Run your first job](../locally/run-your-first-job.md) and make sure
local execution works.
+
+2. Install the plugins required by this recipe. Follow [Deployment > Download
The Connector
Plugins](../locally/deployment.md#download-the-connector-plugins), then keep
only the plugins below in `config/plugin_config`:
+
+```plugin_config
+--seatunnel-connectors--
+connector-cdc-mysql
+connector-doris
+--end--
+```
+
+```bash
+cd "${SEATUNNEL_HOME}"
+sh bin/install-plugin.sh
+ls connectors | rg 'connector-(cdc-mysql|doris)'
+```
+
+3. If you use SeaTunnel Zeta, download the MySQL JDBC driver and place it in
`${SEATUNNEL_HOME}/lib`, then confirm the jar is visible:
+
+```bash
+ls "${SEATUNNEL_HOME}/lib" | rg 'mysql-connector'
+```
+
+If you use Flink or Spark instead of Zeta, put the same driver jar into the
engine plugin directory that your runtime loads.
+
+4. Prepare the MySQL source table. This recipe relies on a stable primary key
so that updates and deletes can be replayed correctly downstream.
+
+```sql
+CREATE DATABASE IF NOT EXISTS inventory;
+
+CREATE TABLE IF NOT EXISTS inventory.orders (
+ id BIGINT PRIMARY KEY,
+ order_status VARCHAR(32),
+ amount DECIMAL(10, 2),
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+);
+
+INSERT INTO inventory.orders (id, order_status, amount, updated_at) VALUES
+ (1001, 'CREATED', 19.99, NOW()),
+ (1002, 'CREATED', 29.99, NOW());
+```
+
+5. Create the MySQL CDC user and grant the same privileges required by the
[MySQL CDC source](../../connectors/source/MySQL-CDC.md):
+
+```sql
+CREATE USER IF NOT EXISTS 'st_user_source'@'%' IDENTIFIED BY 'mysqlpw';
+GRANT SELECT, RELOAD, SHOW DATABASES, REPLICATION SLAVE, REPLICATION CLIENT
+ON *.* TO 'st_user_source'@'%';
+FLUSH PRIVILEGES;
+```
+
+6. Verify that MySQL binlog is ready for CDC:
+
+```sql
+SHOW VARIABLES WHERE variable_name IN ('log_bin', 'binlog_format',
'binlog_row_image');
+```
+
+The expected values are `log_bin = ON`, `binlog_format = ROW`, and
`binlog_row_image = FULL`.
+If they are not set yet, update `my.cnf` and restart MySQL:
+
+```ini
+[mysqld]
+server-id = 223344
+log_bin = mysql-bin
+binlog_format = ROW
+binlog_row_image = FULL
+```
+
+7. Prepare the Doris target database. This recipe keeps `schema_save_mode =
"CREATE_SCHEMA_WHEN_NOT_EXIST"`, so SeaTunnel will create `sync_demo.orders`
automatically from the MySQL primary-key metadata on first startup.
+
+```sql
+CREATE DATABASE IF NOT EXISTS sync_demo;
+```
+
+## Minimal configuration
+
+```hocon
+env {
+ parallelism = 1
+ job.mode = "STREAMING"
+ checkpoint.interval = 5000
+}
+
+source {
+ MySQL-CDC {
+ plugin_output = "orders_cdc"
+ parallelism = 1
+ startup.mode = "initial"
+ server-id = 5652
+ username = "st_user_source"
+ password = "mysqlpw"
+ table-names = ["inventory.orders"]
+ url = "jdbc:mysql://mysql:3306/inventory"
+ }
+}
+
+sink {
+ Doris {
+ plugin_input = "orders_cdc"
+ fenodes = "doris-fe:8030"
+ username = "root"
+ password = ""
+ database = "sync_demo"
+ table = "orders"
+ sink.label-prefix = "orders-cdc"
+ sink.enable-delete = true
+ schema_save_mode = "CREATE_SCHEMA_WHEN_NOT_EXIST"
+ doris.config = {
+ format = "csv"
+ column_separator = ","
+ }
+ }
+}
+```
+
+## Run the job
+
+Save the config as `config/mysql-cdc-to-doris.conf`, then start SeaTunnel in
local mode:
+
+```bash
+cd "${SEATUNNEL_HOME}"
+./bin/seatunnel.sh --config ./config/mysql-cdc-to-doris.conf -m local
+```
+
+Keep the job running while you execute the validation SQL below, because this
is a streaming CDC pipeline.
+
+## Validation result
+
+1. Start the job and wait for the initial snapshot to finish.
+2. Change the source rows in MySQL:
+
+```sql
+INSERT INTO inventory.orders (id, order_status, amount, updated_at)
+VALUES (1003, 'CREATED', 39.99, NOW());
+
+UPDATE inventory.orders
+SET order_status = 'PAID', updated_at = NOW()
+WHERE id = 1001;
+
+DELETE FROM inventory.orders
+WHERE id = 1002;
+```
+
+3. Query Doris and confirm that the latest state is visible there:
+
+```sql
+SELECT COUNT(*) FROM sync_demo.orders;
+SELECT id, order_status, amount FROM sync_demo.orders ORDER BY id;
+```
+
+You should now see row `1001` with status `PAID`, row `1003` with status
`CREATED`, and no remaining row `1002`. If inserts, updates, and deletes from
MySQL are reflected in Doris, the pipeline is working.
+
+## Common pitfalls
+
+- MySQL binlog is not enabled or is not using `ROW` format.
+- The CDC user is missing replication privileges.
+- The `server-id` conflicts with another MySQL replica or another CDC job.
+- `sink.label-prefix` is reused across multiple running jobs, which can cause
Doris stream load conflicts.
+- Delete propagation is enabled, but the Doris table model does not support
the expected delete behavior.
+- The source table has no stable primary key, so Doris auto-create and
downstream upsert behavior are not deterministic.
+
+## Related docs
+
+- [MySQL CDC source](../../connectors/source/MySQL-CDC.md)
+- [Doris sink](../../connectors/sink/Doris.md)
+- [SeaTunnel Engine quick start](../locally/quick-start-seatunnel-engine.md)
diff --git a/docs/en/introduction/about.md b/docs/en/introduction/about.md
index e229144970..99d51a5cf4 100644
--- a/docs/en/introduction/about.md
+++ b/docs/en/introduction/about.md
@@ -11,6 +11,30 @@ SeaTunnel is a multimodal, ultra-high-performance,
distributed data integration
If you are new to SeaTunnel, use this short reading path:
+<div
+ style={{
+ display: "grid",
+ gridTemplateColumns: "repeat(auto-fit, minmax(260px, 1fr))",
+ gap: "16px",
+ margin: "16px 0 24px",
+ }}>
+ <a
+ href="../getting-started/locally/run-your-first-job"
+ style={{
+ display: "block",
+ padding: "18px",
+ border: "1px solid var(--ifm-color-emphasis-300)",
+ borderRadius: "12px",
+ textDecoration: "none",
+ color: "inherit",
+ background: "var(--ifm-background-surface-color)",
+ boxShadow: "var(--ifm-global-shadow-lw)",
+ }}>
+ <strong>Run your first job</strong><br/>
+ <span>Start with a local FakeSource -> FieldMapper -> Console
pipeline, then continue with practical source-to-sink recipes.</span>
+ </a>
+</div>
+
- [Getting Started Overview](../getting-started/overview.md) for the shortest
path into the docs
- [Quick Start With SeaTunnel
Engine](../getting-started/locally/quick-start-seatunnel-engine.md) for the
first local run
- [Job Configuration Guide](../getting-started/job-configuration-guide.md) for
writing real jobs
@@ -87,4 +111,4 @@ SeaTunnel enriches the <a
href="https://landscape.cncf.io/?item=app-definition-a
## Learn more
-You can see [Quick Start](../getting-started/locally/deployment.md) for the
next steps.
+You can see [Run your first
job](../getting-started/locally/run-your-first-job.md) for the next steps.
diff --git a/docs/sidebars.js b/docs/sidebars.js
index 28bcf937f9..863de991a0 100644
--- a/docs/sidebars.js
+++ b/docs/sidebars.js
@@ -109,13 +109,42 @@ const sidebars = {
{
"type": "category",
"label": "Locally",
+ "link": {
+ "type": "generated-index",
+ "title": "Local Getting Started",
+ "description": "Use SeaTunnel locally, run your first
job, and validate common pipelines before moving to larger environments.",
+ "slug": "/getting-started/locally",
+ "keywords": ["getting-started", "local"],
+ "image": "/img/favicon.ico"
+ },
"items": [
+ "getting-started/locally/run-your-first-job",
"getting-started/locally/deployment",
"getting-started/locally/quick-start-seatunnel-engine",
"getting-started/locally/quick-start-flink",
"getting-started/locally/quick-start-spark"
]
},
+ {
+ "type": "category",
+ "label": "Recipes",
+ "link": {
+ "type": "generated-index",
+ "title": "Scenario Recipes",
+ "description": "Practical SeaTunnel recipes for common
source-to-sink pipelines.",
+ "slug": "/getting-started/recipes",
+ "keywords": ["recipes", "examples"],
+ "image": "/img/favicon.ico"
+ },
+ "items": [
+ "getting-started/recipes/mysql-cdc-to-doris",
+ "getting-started/recipes/jdbc-to-s3",
+ "getting-started/recipes/kafka-to-iceberg",
+ "getting-started/recipes/http-to-jdbc",
+ "getting-started/recipes/file-to-starrocks",
+ "getting-started/recipes/multi-table-cdc"
+ ]
+ },
"getting-started/job-configuration-guide",
"getting-started/submit-job-to-remote-zeta-cluster",
{
diff --git a/docs/zh/getting-started/locally/quick-start-seatunnel-engine.md
b/docs/zh/getting-started/locally/quick-start-seatunnel-engine.md
index 9f93b3b45f..1e2ca3cb8d 100644
--- a/docs/zh/getting-started/locally/quick-start-seatunnel-engine.md
+++ b/docs/zh/getting-started/locally/quick-start-seatunnel-engine.md
@@ -108,7 +108,7 @@ cd "apache-seatunnel-${version}"
SeaTunnel控制台将会打印一些如下日志信息:
```shell
-2022-12-19 11:01:45,417 INFO
org.apache.seatunnel.connectors.seatunnel.console.sink.ConsoleSinkWriter -
output rowType: name<STRING>, age<INT>
+2022-12-19 11:01:45,417 INFO
org.apache.seatunnel.connectors.seatunnel.console.sink.ConsoleSinkWriter -
output rowType: new_name<STRING>, age<INT>
2022-12-19 11:01:46,489 INFO
org.apache.seatunnel.connectors.seatunnel.console.sink.ConsoleSinkWriter -
subtaskIndex=0 rowIndex=1: SeaTunnelRow#tableId=-1 SeaTunnelRow#kind=INSERT:
CpiOd, 8520946
2022-12-19 11:01:46,490 INFO
org.apache.seatunnel.connectors.seatunnel.console.sink.ConsoleSinkWriter -
subtaskIndex=0 rowIndex=2: SeaTunnelRow#tableId=-1 SeaTunnelRow#kind=INSERT:
eQqTs, 1256802974
2022-12-19 11:01:46,490 INFO
org.apache.seatunnel.connectors.seatunnel.console.sink.ConsoleSinkWriter -
subtaskIndex=0 rowIndex=3: SeaTunnelRow#tableId=-1 SeaTunnelRow#kind=INSERT:
UsRgO, 2053193072
@@ -243,6 +243,7 @@ Total Failed Count : 0
- 如果你想先建立整体路径感,可以返回阅读[快速入门总览](../overview.md)。
- 当你准备把示例 Source 和 Sink
替换成真实连接器时,建议继续阅读[作业配置指南](../job-configuration-guide.md)。
+- 如果你下一步就想看可直接照着改的源端到目标端示例,可以继续看 [MySQL CDC 到
Doris](../recipes/mysql-cdc-to-doris.md)、[JDBC 到
S3](../recipes/jdbc-to-s3.md)、[Kafka 到
Iceberg](../recipes/kafka-to-iceberg.md)、[Http 到
JDBC](../recipes/http-to-jdbc.md)、[File 到
StarRocks](../recipes/file-to-starrocks.md) 和 [多表
CDC](../recipes/multi-table-cdc.md)。
- 开始编写您自己的配置文件,选择您想要使用的[连接器](../../connectors/source),并根据连接器的文档配置参数。
- 如果您要部署多节点 SeaTunnel Engine 集群,请继续阅读[SeaTunnel Engine(Zeta)
安装部署](../../engines/zeta/deployment.md)。
- 如果您想进一步了解 SeaTunnel Engine,请参阅[SeaTunnel引擎](../../engines/zeta/about.md)。
diff --git a/docs/zh/getting-started/locally/run-your-first-job.md
b/docs/zh/getting-started/locally/run-your-first-job.md
new file mode 100644
index 0000000000..aaadfadf00
--- /dev/null
+++ b/docs/zh/getting-started/locally/run-your-first-job.md
@@ -0,0 +1,99 @@
+---
+sidebar_position: 1
+title: 跑第一个任务
+---
+
+# 跑第一个任务
+
+这一页只解决一件事:用最短路径把 SeaTunnel 真正跑起来。这个示例完全本地运行,不依赖 MySQL、Kafka
或对象存储,适合先确认安装、配置解析和执行引擎都正常。
+
+## 步骤 1:先完成本地部署
+
+先完成 [部署](deployment.md),并确认 SeaTunnel 目录下已经有 `bin/seatunnel.sh`。
+
+## 步骤 2:只安装这篇示例真正需要的插件
+
+先看 [部署 > 下载连接器插件](deployment.md#下载连接器插件),然后把 `config/plugin_config` 收敛成下面两个插件:
+
+```plugin_config
+--seatunnel-connectors--
+connector-fake
+connector-console
+--end--
+```
+
+接着执行安装命令,并确认插件已经下载到 `${SEATUNNEL_HOME}/connectors`:
+
+```bash
+cd "${SEATUNNEL_HOME}"
+sh bin/install-plugin.sh
+ls connectors | rg 'connector-(fake|console)'
+```
+
+## 步骤 3:使用最小可运行配置
+
+把下面的配置保存为 `config/v2.batch.config.template`,或者保存为你自己的本地配置文件:
+
+```hocon
+env {
+ parallelism = 1
+ job.mode = "BATCH"
+}
+
+source {
+ FakeSource {
+ plugin_output = "fake"
+ row.num = 16
+ schema = {
+ fields {
+ name = "string"
+ age = "int"
+ }
+ }
+ }
+}
+
+transform {
+ FieldMapper {
+ plugin_input = "fake"
+ plugin_output = "fake1"
+ field_mapper = {
+ age = age
+ name = new_name
+ }
+ }
+}
+
+sink {
+ Console {
+ plugin_input = "fake1"
+ }
+}
+```
+
+## 步骤 4:用本地模式运行
+
+```shell
+cd "apache-seatunnel-${version}"
+./bin/seatunnel.sh --config ./config/v2.batch.config.template -m local
+```
+
+## 验证结果
+
+- 任务可以正常启动,没有 connector 加载错误。
+- 控制台会打印映射后字段的 `output rowType` 行。
+- 控制台会打印 16 行 `ConsoleSinkWriter` 输出。
+- 批任务在写完全部数据后正常退出。
+
+如果这里已经跑通,说明本地基础链路是正常的,后面就可以切到真实数据源和真实目标端。
+
+## 下一步
+
+- 如果你想看完整本地链路,请继续看 [SeaTunnel 引擎快速开始](quick-start-seatunnel-engine.md)。
+- 如果你想直接看真实源端到目标端示例,请从下面这些教程开始:
+ - [MySQL CDC 到 Doris](../recipes/mysql-cdc-to-doris.md)
+ - [JDBC 到 S3](../recipes/jdbc-to-s3.md)
+ - [Kafka 到 Iceberg](../recipes/kafka-to-iceberg.md)
+ - [Http 到 JDBC](../recipes/http-to-jdbc.md)
+ - [File 到 StarRocks](../recipes/file-to-starrocks.md)
+ - [多表 CDC](../recipes/multi-table-cdc.md)
diff --git a/docs/zh/getting-started/recipes/file-to-starrocks.md
b/docs/zh/getting-started/recipes/file-to-starrocks.md
new file mode 100644
index 0000000000..167356cf99
--- /dev/null
+++ b/docs/zh/getting-started/recipes/file-to-starrocks.md
@@ -0,0 +1,145 @@
+---
+sidebar_position: 5
+title: File 到 StarRocks
+---
+
+# File 到 StarRocks
+
+当你想把本地 CSV 或文本文件导入 StarRocks,供后续高性能分析查询使用时,可以使用这条链路。
+
+## 前置条件
+
+1. 先完成 [跑第一个任务](../locally/run-your-first-job.md)。
+
+2. 安装这条链路需要的插件。先看 [部署 > 下载连接器插件](../locally/deployment.md#下载连接器插件),然后把
`config/plugin_config` 改成下面这样:
+
+```plugin_config
+--seatunnel-connectors--
+connector-file-local
+connector-starrocks
+--end--
+```
+
+```bash
+cd "${SEATUNNEL_HOME}"
+sh bin/install-plugin.sh
+ls connectors | rg 'connector-(file-local|starrocks)'
+```
+
+3. 把 StarRocks sink 依赖的 MySQL JDBC 驱动放进 `${SEATUNNEL_HOME}/lib`,并确认 jar 已经落盘:
+
+```bash
+ls "${SEATUNNEL_HOME}/lib" | rg 'mysql-connector'
+```
+
+4. 先准备本地输入文件,并确保 SeaTunnel 进程能读到它:
+
+```bash
+mkdir -p /tmp/seatunnel/input
+cat <<'EOF' > /tmp/seatunnel/input/customers.csv
+id,name,city,updated_at
+1001,Alice,Shanghai,2026-06-12 10:00:00
+1002,Bob,Beijing,2026-06-12 10:05:00
+1003,Carol,Hangzhou,2026-06-12 10:10:00
+EOF
+```
+
+5. 运行任务前,先在 StarRocks 中创建好目标库和目标表。
+
+## 最小配置
+
+下面的示例读取一个带表头的本地 CSV 文件,并把数据写入已经存在的 StarRocks 主键表。
+
+先创建目标表:
+
+```sql
+CREATE DATABASE IF NOT EXISTS sync_demo;
+
+CREATE TABLE IF NOT EXISTS sync_demo.customers (
+ id BIGINT NOT NULL,
+ name STRING,
+ city STRING,
+ updated_at DATETIME
+)
+ENGINE=OLAP
+PRIMARY KEY(id)
+DISTRIBUTED BY HASH(id)
+PROPERTIES (
+ "replication_num" = "1"
+);
+```
+
+```hocon
+env {
+ parallelism = 1
+ job.mode = "BATCH"
+}
+
+source {
+ LocalFile {
+ plugin_output = "customers_file"
+ path = "/tmp/seatunnel/input/customers.csv"
+ file_format_type = "csv"
+ csv_use_header_line = true
+ schema = {
+ fields {
+ id = bigint
+ name = string
+ city = string
+ updated_at = timestamp
+ }
+ }
+ }
+}
+
+sink {
+ StarRocks {
+ plugin_input = "customers_file"
+ nodeUrls = ["starrocks-fe:8030"]
+ base-url = "jdbc:mysql://starrocks-fe:9030/sync_demo"
+ username = "root"
+ password = ""
+ database = "sync_demo"
+ table = "customers"
+ batch_max_rows = 1000
+ schema_save_mode = "IGNORE"
+ starrocks.config = {
+ format = "JSON"
+ strip_outer_array = true
+ }
+ }
+}
+```
+
+## 运行任务
+
+把配置保存为 `config/file-to-starrocks.conf`,然后用本地模式运行 SeaTunnel:
+
+```bash
+cd "${SEATUNNEL_HOME}"
+./bin/seatunnel.sh --config ./config/file-to-starrocks.conf -m local
+```
+
+## 验证结果
+
+1. 运行任务,确认没有 StarRocks stream load 错误。
+2. 在 StarRocks 中查询目标表。
+
+```sql
+SELECT COUNT(*) FROM sync_demo.customers;
+SELECT id, name, city, updated_at FROM sync_demo.customers ORDER BY id;
+```
+
+如果 StarRocks 中的导入结果和文件内容一致,这条链路就是通的。
+
+## 常见坑
+
+- 配了 `nodeUrls`,但漏了 `base-url`。
+- 文件带表头,但没有设置 `csv_use_header_line = true`。
+- 源文件 schema、分隔符、时间格式和实际文件内容不一致。
+- 运行前没有先创建目标表。本教程使用 `schema_save_mode = "IGNORE"`,因为本地文件源不会提供 StarRocks
自动建表需要的主键元数据。
+
+## 相关文档
+
+- [LocalFile Source](../../connectors/source/LocalFile.md)
+- [StarRocks Sink](../../connectors/sink/StarRocks.md)
diff --git a/docs/zh/getting-started/recipes/http-to-jdbc.md
b/docs/zh/getting-started/recipes/http-to-jdbc.md
new file mode 100644
index 0000000000..420d02ee18
--- /dev/null
+++ b/docs/zh/getting-started/recipes/http-to-jdbc.md
@@ -0,0 +1,126 @@
+---
+sidebar_position: 4
+title: Http 到 JDBC
+---
+
+# Http 到 JDBC
+
+当你想从 HTTP API 拉取结构化数据,并把结果落到关系型数据库中时,可以使用这条链路。
+
+## 前置条件
+
+1. 先完成 [跑第一个任务](../locally/run-your-first-job.md)。
+
+2. 安装这条链路需要的插件。先看 [部署 > 下载连接器插件](../locally/deployment.md#下载连接器插件),然后把
`config/plugin_config` 改成下面这样:
+
+```plugin_config
+--seatunnel-connectors--
+connector-http-base
+connector-jdbc
+--end--
+```
+
+```bash
+cd "${SEATUNNEL_HOME}"
+sh bin/install-plugin.sh
+ls connectors | rg 'connector-(http-base|jdbc)'
+```
+
+3. 把目标数据库 JDBC 驱动放进 `${SEATUNNEL_HOME}/lib`,并确认 jar 已经落盘:
+
+```bash
+ls "${SEATUNNEL_HOME}/lib" | rg 'postgresql'
+```
+
+4. 运行任务前,先看一眼 HTTP 返回内容。这里直接使用 [Http Source](../../connectors/source/Http.md)
里的示例接口,返回 JSON 顶层应该能看到 `c_string` 和 `c_int` 这些字段:
+
+```bash
+curl http://mockserver:1080/example/http
+```
+
+如果你的真实接口把有效数据包在更深层字段里,就要先补 `json_field` 或 `content_field`,否则别急着运行。
+
+5. 先准备 PostgreSQL 目标库,并给 sink 用户授予在 `public` schema 自动建表的权限,因为这篇教程使用了
`generate_sink_sql = true`:
+
+```sql
+CREATE USER test WITH PASSWORD 'test';
+CREATE DATABASE test OWNER test;
+```
+
+重新连接到 `test` 库以后,再执行:
+
+```sql
+GRANT USAGE, CREATE ON SCHEMA public TO test;
+```
+
+## 最小配置
+
+```hocon
+env {
+ parallelism = 1
+ job.mode = "BATCH"
+}
+
+source {
+ Http {
+ plugin_output = "http_orders"
+ url = "http://mockserver:1080/example/http"
+ method = "GET"
+ format = "json"
+ schema = {
+ fields {
+ c_string = string
+ c_int = int
+ }
+ }
+ }
+}
+
+sink {
+ Jdbc {
+ plugin_input = "http_orders"
+ driver = "org.postgresql.Driver"
+ url = "jdbc:postgresql://postgresql:5432/test?loggerLevel=OFF"
+ username = "test"
+ password = "test"
+ generate_sink_sql = true
+ database = "test"
+ table = "public.http_orders"
+ primary_keys = ["c_string"]
+ batch_size = 100
+ }
+}
+```
+
+## 运行任务
+
+把配置保存为 `config/http-to-jdbc.conf`,然后用本地模式运行 SeaTunnel:
+
+```bash
+cd "${SEATUNNEL_HOME}"
+./bin/seatunnel.sh --config ./config/http-to-jdbc.conf -m local
+```
+
+## 验证结果
+
+1. 运行任务,确认没有 HTTP 解析错误和 JDBC DDL 错误。
+2. 查询目标表,核对行数和 API 返回结果。
+
+```sql
+SELECT COUNT(*) FROM public.http_orders;
+SELECT c_string, c_int FROM public.http_orders ORDER BY c_string;
+```
+
+如果目标表里的数据和 HTTP 返回内容一致,这条链路就是通的。使用默认 mock 返回时,查询结果里应该能看到和 `curl` 输出一致的
`c_string`、`c_int` 值。
+
+## 常见坑
+
+- 返回体是 JSON,但 schema 中字段名或字段类型写错了。
+- API 数据是嵌套结构,但没有配置 `content_field` 或 `json_field`。
+- 源接口有分页或限流,但作业按单页接口处理。
+- JDBC sink 虽然自动建表了,但你选的主键并不能真正唯一标识一条记录。
+
+## 相关文档
+
+- [Http Source](../../connectors/source/Http.md)
+- [JDBC Sink](../../connectors/sink/Jdbc.md)
diff --git a/docs/zh/getting-started/recipes/jdbc-to-s3.md
b/docs/zh/getting-started/recipes/jdbc-to-s3.md
new file mode 100644
index 0000000000..2ea7a42e79
--- /dev/null
+++ b/docs/zh/getting-started/recipes/jdbc-to-s3.md
@@ -0,0 +1,137 @@
+---
+sidebar_position: 2
+title: JDBC 到 S3
+---
+
+# JDBC 到 S3
+
+当你想把关系型数据库里的表数据批量导出到 S3 或兼容 S3 的对象存储时,可以使用这条链路。
+
+## 前置条件
+
+1. 先完成 [跑第一个任务](../locally/run-your-first-job.md)。
+
+2. 安装这条链路需要的插件。先看 [部署 > 下载连接器插件](../locally/deployment.md#下载连接器插件),然后把
`config/plugin_config` 改成下面这样:
+
+```plugin_config
+--seatunnel-connectors--
+connector-jdbc
+connector-file-s3
+--end--
+```
+
+```bash
+cd "${SEATUNNEL_HOME}"
+sh bin/install-plugin.sh
+ls connectors | rg 'connector-(jdbc|file-s3)'
+```
+
+3. 把源端数据库 JDBC 驱动放进 `${SEATUNNEL_HOME}/lib`,并确认 jar 已经落盘:
+
+```bash
+ls "${SEATUNNEL_HOME}/lib" | rg 'mysql-connector'
+```
+
+4. 把 S3 连接器依赖的 `hadoop-aws` 和 AWS SDK bundle 也放进
`${SEATUNNEL_HOME}/lib`,然后确认这两个依赖都能看到:
+
+```bash
+ls "${SEATUNNEL_HOME}/lib" | rg 'hadoop-aws|aws-java-sdk-bundle'
+```
+
+5. 先准备源端数据库表。这个示例使用 MySQL,并从 `analytics.orders` 导出两条数据:
+
+```sql
+CREATE DATABASE IF NOT EXISTS analytics;
+
+CREATE TABLE IF NOT EXISTS analytics.orders (
+ id BIGINT PRIMARY KEY,
+ customer_id BIGINT,
+ total_amount DECIMAL(16, 2),
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+
+INSERT INTO analytics.orders (id, customer_id, total_amount, updated_at) VALUES
+ (5001, 101, 19.99, NOW()),
+ (5002, 102, 29.99, NOW());
+```
+
+6. 准备好可写入的 S3 bucket 和访问凭据。下面的示例默认目标 bucket 已存在,而且 `access_key` 与 `secret_key`
对 `s3://company-data-lake/seatunnel/orders/` 有写权限。
+
+## 最小配置
+
+这个示例把 MySQL 查询结果导出成 JSON Lines,便于直接检查输出内容。
+
+```hocon
+env {
+ parallelism = 1
+ job.mode = "BATCH"
+}
+
+source {
+ Jdbc {
+ plugin_output = "orders_jdbc"
+ url = "jdbc:mysql://mysql:3306/analytics"
+ driver = "com.mysql.cj.jdbc.Driver"
+ username = "root"
+ password = "password"
+ query = "select id, customer_id, total_amount, updated_at from orders"
+ }
+}
+
+sink {
+ S3File {
+ plugin_input = "orders_jdbc"
+ bucket = "s3a://company-data-lake"
+ path = "/seatunnel/orders/"
+ fs.s3a.endpoint = "s3.us-east-1.amazonaws.com"
+ fs.s3a.aws.credentials.provider =
"org.apache.hadoop.fs.s3a.SimpleAWSCredentialsProvider"
+ access_key = "your-access-key"
+ secret_key = "your-secret-key"
+ file_format_type = "json"
+ row_delimiter = "\n"
+ custom_filename = true
+ file_name_expression = "orders"
+ filename_extension = "json"
+ single_file_mode = true
+ is_enable_transaction = false
+ schema_save_mode = "CREATE_SCHEMA_WHEN_NOT_EXIST"
+ data_save_mode = "APPEND_DATA"
+ }
+}
+```
+
+## 运行任务
+
+把配置保存为 `config/jdbc-to-s3.conf`,然后用本地模式运行 SeaTunnel:
+
+```bash
+cd "${SEATUNNEL_HOME}"
+./bin/seatunnel.sh --config ./config/jdbc-to-s3.conf -m local
+```
+
+## 验证结果
+
+1. 先在数据库里直接执行源查询,记录预期行数。
+2. 启动 SeaTunnel 作业。
+3. 检查目标 S3 前缀下是否生成了新对象。
+
+```bash
+aws s3 ls s3://company-data-lake/seatunnel/orders/ --recursive
+aws s3 cp s3://company-data-lake/seatunnel/orders/orders.json - | head
+```
+
+如果目标前缀下生成了对象,且内容和源查询结果一致,这条链路就是通的。
+
+## 常见坑
+
+- JDBC 驱动在本机有,但没有放进 `${SEATUNNEL_HOME}/lib`。
+- `bucket` 和 `path` 写反了。`bucket` 写桶,`path` 写桶内前缀。
+- 凭据提供器和你实际配置的认证方式不匹配。
+- 大表直接跑一个无边界 `query`,没有做过滤或分片。
+- 固定文件名只适合这个单文件教程。如果重新开启事务,`file_name_expression` 里必须保留 `${transactionId}`。
+- 目标是兼容 S3 的对象存储,但 `fs.s3a.endpoint` 还在指向 AWS 默认地址。
+
+## 相关文档
+
+- [JDBC Source](../../connectors/source/Jdbc.md)
+- [S3File Sink](../../connectors/sink/S3File.md)
diff --git a/docs/zh/getting-started/recipes/kafka-to-iceberg.md
b/docs/zh/getting-started/recipes/kafka-to-iceberg.md
new file mode 100644
index 0000000000..bb056feba8
--- /dev/null
+++ b/docs/zh/getting-started/recipes/kafka-to-iceberg.md
@@ -0,0 +1,138 @@
+---
+sidebar_position: 3
+title: Kafka 到 Iceberg
+---
+
+# Kafka 到 Iceberg
+
+当你想把 Kafka 里的流式事件落到 Iceberg 表中,供后续分析查询使用时,可以使用这条链路。
+
+## 前置条件
+
+1. 先完成 [跑第一个任务](../locally/run-your-first-job.md)。
+
+2. 安装这条链路需要的插件。先看 [部署 > 下载连接器插件](../locally/deployment.md#下载连接器插件),然后把
`config/plugin_config` 改成下面这样:
+
+```plugin_config
+--seatunnel-connectors--
+connector-kafka
+connector-iceberg
+--end--
+```
+
+```bash
+cd "${SEATUNNEL_HOME}"
+sh bin/install-plugin.sh
+ls connectors | rg 'connector-(kafka|iceberg)'
+```
+
+3. 如果你使用 Flink 或 Spark,请补齐 Iceberg 在对应环境里需要的依赖,例如 `hive-exec` 和 `libfb303`。
+
+4. 先选一个空的、当前进程可写的 Iceberg warehouse 目录。这篇教程使用本地 Hadoop catalog,对应路径是
`file:///tmp/seatunnel/iceberg/warehouse-demo`:
+
+```bash
+mkdir -p /tmp/seatunnel/iceberg/warehouse-demo
+```
+
+5. 在启动任务前,先创建 Kafka topic 并写入几条 JSON 消息:
+
+```bash
+kafka-topics.sh \
+ --create \
+ --if-not-exists \
+ --topic orders \
+ --bootstrap-server kafka:9092 \
+ --partitions 1 \
+ --replication-factor 1
+
+kafka-console-producer.sh --topic orders --bootstrap-server kafka:9092 <<'EOF'
+{"id":1001,"customer_id":2001,"total_amount":19.99,"event_date":"2026-06-12"}
+{"id":1002,"customer_id":2002,"total_amount":29.99,"event_date":"2026-06-12"}
+EOF
+```
+
+## 最小配置
+
+```hocon
+env {
+ parallelism = 1
+ job.mode = "STREAMING"
+ checkpoint.interval = 5000
+}
+
+source {
+ Kafka {
+ plugin_output = "orders_kafka"
+ topic = "orders"
+ bootstrap.servers = "kafka:9092"
+ consumer.group = "seatunnel-orders"
+ start_mode = "earliest"
+ format = "json"
+ schema = {
+ fields {
+ id = bigint
+ customer_id = bigint
+ total_amount = "decimal(16, 2)"
+ event_date = string
+ }
+ }
+ }
+}
+
+sink {
+ Iceberg {
+ plugin_input = "orders_kafka"
+ catalog_name = "seatunnel_demo"
+ namespace = "lakehouse"
+ table = "orders"
+ iceberg.catalog.config = {
+ type = "hadoop"
+ warehouse = "file:///tmp/seatunnel/iceberg/warehouse-demo"
+ }
+ iceberg.table.primary-keys = "id"
+ iceberg.table.partition-keys = "event_date"
+ iceberg.table.upsert-mode-enabled = true
+ iceberg.table.schema-evolution-enabled = true
+ case_sensitive = true
+ }
+}
+```
+
+## 运行任务
+
+把配置保存为 `config/kafka-to-iceberg.conf`,然后用本地模式启动 SeaTunnel:
+
+```bash
+cd "${SEATUNNEL_HOME}"
+./bin/seatunnel.sh --config ./config/kafka-to-iceberg.conf -m local
+```
+
+这是一条流式任务,所以 Kafka 消息被消费和落表时,任务需要保持运行中。
+
+## 验证结果
+
+1. 检查 warehouse 路径下是否生成了 Iceberg 元数据和数据文件。
+2. 使用 Spark、Trino 或其他 Iceberg 兼容引擎查询表。
+
+```bash
+ls /tmp/seatunnel/iceberg/warehouse-demo/lakehouse/orders
+spark-sql \
+ --conf
spark.sql.catalog.seatunnel_demo=org.apache.iceberg.spark.SparkCatalog \
+ --conf spark.sql.catalog.seatunnel_demo.type=hadoop \
+ --conf
spark.sql.catalog.seatunnel_demo.warehouse=file:///tmp/seatunnel/iceberg/warehouse-demo
\
+ -e "SELECT COUNT(*) FROM seatunnel_demo.lakehouse.orders"
+```
+
+如果表可以正常查询,且行数和你写入 Kafka 的消息数量一致,这条链路就是通的。按照上面两条样例消息,最终行数应该是 `2`。
+
+## 常见坑
+
+- Kafka 中的 JSON 消息结构和 source 里定义的 schema 不一致。
+- 流作业没有开启 checkpoint,导致重启和一致性行为变弱。
+- Iceberg catalog 类型对了,但 warehouse 路径对当前引擎进程不可写。
+- 开启了 upsert 模式,但消息并没有稳定主键。
+
+## 相关文档
+
+- [Kafka Source](../../connectors/source/Kafka.md)
+- [Iceberg Sink](../../connectors/sink/Iceberg.md)
diff --git a/docs/zh/getting-started/recipes/multi-table-cdc.md
b/docs/zh/getting-started/recipes/multi-table-cdc.md
new file mode 100644
index 0000000000..0a5e30c1cc
--- /dev/null
+++ b/docs/zh/getting-started/recipes/multi-table-cdc.md
@@ -0,0 +1,204 @@
+---
+sidebar_position: 6
+title: 多表 CDC
+---
+
+# 多表 CDC
+
+当你想用一个 SeaTunnel 作业同时采集多个上游表的变更,并自动把每张表路由到各自下游表时,可以使用这条链路。
+
+## 前置条件
+
+1. 先完成 [跑第一个任务](../locally/run-your-first-job.md)。
+
+2. 安装这条链路需要的插件。先看 [部署 > 下载连接器插件](../locally/deployment.md#下载连接器插件),然后把
`config/plugin_config` 改成下面这样:
+
+```plugin_config
+--seatunnel-connectors--
+connector-cdc-mysql
+connector-jdbc
+--end--
+```
+
+```bash
+cd "${SEATUNNEL_HOME}"
+sh bin/install-plugin.sh
+ls connectors | rg 'connector-(cdc-mysql|jdbc)'
+```
+
+3. 如果你用的是 SeaTunnel Zeta,再把 MySQL JDBC 驱动和 PostgreSQL JDBC 驱动都放进
`${SEATUNNEL_HOME}/lib`,并确认 jar 已经能看到:
+
+```bash
+ls "${SEATUNNEL_HOME}/lib" | rg 'mysql-connector|postgresql'
+```
+
+4. 先准备 MySQL 源表。因为这条链路会自动把 CDC 事件路由到下游 upsert 表,所以每张上游表都要有稳定主键。
+
+```sql
+CREATE DATABASE IF NOT EXISTS inventory;
+
+CREATE TABLE IF NOT EXISTS inventory.orders (
+ id BIGINT PRIMARY KEY,
+ order_status VARCHAR(32),
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+);
+
+CREATE TABLE IF NOT EXISTS inventory.customers (
+ id BIGINT PRIMARY KEY,
+ customer_name VARCHAR(64),
+ city VARCHAR(64),
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+);
+
+CREATE TABLE IF NOT EXISTS inventory.products (
+ id BIGINT PRIMARY KEY,
+ product_name VARCHAR(64),
+ unit_price DECIMAL(10, 2),
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+);
+
+INSERT INTO inventory.orders (id, order_status, updated_at) VALUES
+ (2001, 'CREATED', NOW());
+
+INSERT INTO inventory.customers (id, customer_name, city, updated_at) VALUES
+ (3001, 'Alice', 'Shanghai', NOW());
+
+INSERT INTO inventory.products (id, product_name, unit_price, updated_at)
VALUES
+ (4001, 'Keyboard', 99.00, NOW());
+```
+
+5. 创建 MySQL CDC 用户并授权:
+
+```sql
+CREATE USER IF NOT EXISTS 'st_user_source'@'%' IDENTIFIED BY 'mysqlpw';
+GRANT SELECT, RELOAD, SHOW DATABASES, REPLICATION SLAVE, REPLICATION CLIENT
+ON *.* TO 'st_user_source'@'%';
+FLUSH PRIVILEGES;
+```
+
+6. 检查 MySQL binlog 是否满足 CDC 要求:
+
+```sql
+SHOW VARIABLES WHERE variable_name IN ('log_bin', 'binlog_format',
'binlog_row_image');
+```
+
+期望值是 `log_bin = ON`、`binlog_format = ROW`、`binlog_row_image = FULL`。
+
+7. 准备 PostgreSQL 目标库,并给 sink 用户授予在 `public` schema 自动建表的权限:
+
+```sql
+CREATE USER st_user_sink WITH PASSWORD 'pgpw';
+CREATE DATABASE sync_demo;
+GRANT ALL PRIVILEGES ON DATABASE sync_demo TO st_user_sink;
+```
+
+重新连到 `sync_demo` 以后,再执行:
+
+```sql
+GRANT USAGE, CREATE ON SCHEMA public TO st_user_sink;
+```
+
+这篇教程使用 `generate_sink_sql = true`,所以第一次运行时 SeaTunnel 会自动创建
`public.st_orders`、`public.st_customers` 这类目标表。
+
+## 最小配置
+
+下面这个示例通过一个 `table-pattern` 同时读取多张 MySQL 表,并把它们分别写入 PostgreSQL 中名为 `st_<上游表名>`
的目标表。
+
+```hocon
+env {
+ parallelism = 1
+ job.mode = "STREAMING"
+ checkpoint.interval = 5000
+}
+
+source {
+ MySQL-CDC {
+ plugin_output = "mysql_multi"
+ startup.mode = "initial"
+ server-id = 5652
+ username = "st_user_source"
+ password = "mysqlpw"
+ database-pattern = "inventory"
+ table-pattern = "inventory\\.(orders|customers|products)"
+ url = "jdbc:mysql://mysql:3306/inventory"
+ }
+}
+
+sink {
+ Jdbc {
+ plugin_input = "mysql_multi"
+ driver = "org.postgresql.Driver"
+ url = "jdbc:postgresql://postgresql:5432/sync_demo"
+ username = "st_user_sink"
+ password = "pgpw"
+ generate_sink_sql = true
+ database = "sync_demo"
+ table = "public.st_${table_name}"
+ primary_keys = ["${primary_key}"]
+ schema_save_mode = "CREATE_SCHEMA_WHEN_NOT_EXIST"
+ data_save_mode = "APPEND_DATA"
+ }
+}
+```
+
+## 运行任务
+
+把配置保存为 `config/multi-table-cdc.conf`,然后用本地模式启动 SeaTunnel:
+
+```bash
+cd "${SEATUNNEL_HOME}"
+./bin/seatunnel.sh --config ./config/multi-table-cdc.conf -m local
+```
+
+这是一条流式 CDC 作业,所以执行下面的验证步骤时,任务需要保持运行中。
+
+## 验证结果
+
+1. 启动作业,等首轮快照完成。
+2. 先确认 PostgreSQL 里已经自动创建出多张目标表:
+
+```sql
+SELECT table_name
+FROM information_schema.tables
+WHERE table_schema = 'public' AND table_name LIKE 'st_%'
+ORDER BY table_name;
+```
+
+3. 分别在 MySQL 源表执行几条变更:
+
+```sql
+INSERT INTO inventory.orders (id, order_status, updated_at)
+VALUES (2002, 'PAID', NOW());
+
+UPDATE inventory.customers
+SET city = 'Hangzhou', updated_at = NOW()
+WHERE id = 3001;
+
+INSERT INTO inventory.products (id, product_name, unit_price, updated_at)
+VALUES (4002, 'Mouse', 59.00, NOW());
+```
+
+4. 检查每张目标表是否只接收到自己的数据:
+
+```sql
+SELECT id, order_status FROM public.st_orders ORDER BY id;
+SELECT id, customer_name, city FROM public.st_customers ORDER BY id;
+SELECT id, product_name, unit_price FROM public.st_products ORDER BY id;
+```
+
+如果每张上游表都能进入对应的下游表,并且后续变更还能持续同步,这条多表 CDC 链路就是通的。
+
+## 常见坑
+
+- `table-pattern` 的正则没有转义好。在 HOCON 里,字面量 `.` 通常要写成 `\\.`。
+- MySQL binlog 或 CDC 用户权限不完整,导致任务只能读快照,后续增量读不出来。
+- 没有配置基于占位符的 sink 路由,结果多张源表被写进了同一张目标表。
+- PostgreSQL sink 用户虽然能连库,但没有 `public` schema 的 `CREATE` 权限,自动建表会失败。
+- 上游表没有主键,但下游配置却按 upsert 语义来用。
+- 不同数据库对 schema 和 table 占位符的命名规则不同,直接照搬会失败。
+
+## 相关文档
+
+- [MySQL CDC Source](../../connectors/source/MySQL-CDC.md)
+- [JDBC Sink](../../connectors/sink/Jdbc.md)
+- [多表同步架构](../../architecture/features/multi-table.md)
diff --git a/docs/zh/getting-started/recipes/mysql-cdc-to-doris.md
b/docs/zh/getting-started/recipes/mysql-cdc-to-doris.md
new file mode 100644
index 0000000000..c3f0f32d6b
--- /dev/null
+++ b/docs/zh/getting-started/recipes/mysql-cdc-to-doris.md
@@ -0,0 +1,177 @@
+---
+sidebar_position: 1
+title: MySQL CDC 到 Doris
+---
+
+# MySQL CDC 到 Doris
+
+当你想把 MySQL 的行级变更持续同步到 Doris,并让 Doris 始终保持最新状态时,可以直接使用这条链路。
+
+## 前置条件
+
+1. 先完成 [跑第一个任务](../locally/run-your-first-job.md),确认本地基础链路正常。
+
+2. 安装这条链路需要的插件。先看 [部署 > 下载连接器插件](../locally/deployment.md#下载连接器插件),然后把
`config/plugin_config` 改成下面这样:
+
+```plugin_config
+--seatunnel-connectors--
+connector-cdc-mysql
+connector-doris
+--end--
+```
+
+```bash
+cd "${SEATUNNEL_HOME}"
+sh bin/install-plugin.sh
+ls connectors | rg 'connector-(cdc-mysql|doris)'
+```
+
+3. 如果你用的是 SeaTunnel Zeta,再把 MySQL JDBC 驱动放进 `${SEATUNNEL_HOME}/lib`,并确认 jar
已经落盘:
+
+```bash
+ls "${SEATUNNEL_HOME}/lib" | rg 'mysql-connector'
+```
+
+如果你用的是 Flink 或 Spark,就把同一个驱动 jar 放到对应引擎实际加载的插件目录里。
+
+4. 先准备 MySQL 源表。这条教程依赖稳定主键,这样下游才能正确回放更新和删除事件。
+
+```sql
+CREATE DATABASE IF NOT EXISTS inventory;
+
+CREATE TABLE IF NOT EXISTS inventory.orders (
+ id BIGINT PRIMARY KEY,
+ order_status VARCHAR(32),
+ amount DECIMAL(10, 2),
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+);
+
+INSERT INTO inventory.orders (id, order_status, amount, updated_at) VALUES
+ (1001, 'CREATED', 19.99, NOW()),
+ (1002, 'CREATED', 29.99, NOW());
+```
+
+5. 按照 [MySQL CDC Source](../../connectors/source/MySQL-CDC.md) 的要求创建 CDC 用户并授权:
+
+```sql
+CREATE USER IF NOT EXISTS 'st_user_source'@'%' IDENTIFIED BY 'mysqlpw';
+GRANT SELECT, RELOAD, SHOW DATABASES, REPLICATION SLAVE, REPLICATION CLIENT
+ON *.* TO 'st_user_source'@'%';
+FLUSH PRIVILEGES;
+```
+
+6. 检查 MySQL binlog 是否已经满足 CDC 要求:
+
+```sql
+SHOW VARIABLES WHERE variable_name IN ('log_bin', 'binlog_format',
'binlog_row_image');
+```
+
+期望值是 `log_bin = ON`、`binlog_format = ROW`、`binlog_row_image = FULL`。
+如果还没有配置好,就修改 `my.cnf` 并重启 MySQL:
+
+```ini
+[mysqld]
+server-id = 223344
+log_bin = mysql-bin
+binlog_format = ROW
+binlog_row_image = FULL
+```
+
+7. 先准备 Doris 目标库。这篇教程保留 `schema_save_mode =
"CREATE_SCHEMA_WHEN_NOT_EXIST"`,所以第一次启动时会用 MySQL 主键信息自动创建 `sync_demo.orders`。
+
+```sql
+CREATE DATABASE IF NOT EXISTS sync_demo;
+```
+
+## 最小配置
+
+```hocon
+env {
+ parallelism = 1
+ job.mode = "STREAMING"
+ checkpoint.interval = 5000
+}
+
+source {
+ MySQL-CDC {
+ plugin_output = "orders_cdc"
+ parallelism = 1
+ startup.mode = "initial"
+ server-id = 5652
+ username = "st_user_source"
+ password = "mysqlpw"
+ table-names = ["inventory.orders"]
+ url = "jdbc:mysql://mysql:3306/inventory"
+ }
+}
+
+sink {
+ Doris {
+ plugin_input = "orders_cdc"
+ fenodes = "doris-fe:8030"
+ username = "root"
+ password = ""
+ database = "sync_demo"
+ table = "orders"
+ sink.label-prefix = "orders-cdc"
+ sink.enable-delete = true
+ schema_save_mode = "CREATE_SCHEMA_WHEN_NOT_EXIST"
+ doris.config = {
+ format = "csv"
+ column_separator = ","
+ }
+ }
+}
+```
+
+## 运行任务
+
+把配置保存为 `config/mysql-cdc-to-doris.conf`,然后用本地模式启动 SeaTunnel:
+
+```bash
+cd "${SEATUNNEL_HOME}"
+./bin/seatunnel.sh --config ./config/mysql-cdc-to-doris.conf -m local
+```
+
+这是一条流式 CDC 作业,所以执行下面的验证 SQL 时,任务需要保持运行中。
+
+## 验证结果
+
+1. 启动作业,等首轮快照完成。
+2. 在 MySQL 中执行下面几条变更:
+
+```sql
+INSERT INTO inventory.orders (id, order_status, amount, updated_at)
+VALUES (1003, 'CREATED', 39.99, NOW());
+
+UPDATE inventory.orders
+SET order_status = 'PAID', updated_at = NOW()
+WHERE id = 1001;
+
+DELETE FROM inventory.orders
+WHERE id = 1002;
+```
+
+3. 在 Doris 中查询最新结果:
+
+```sql
+SELECT COUNT(*) FROM sync_demo.orders;
+SELECT id, order_status, amount FROM sync_demo.orders ORDER BY id;
+```
+
+此时你应该能看到 `1001` 变成 `PAID`,`1003` 成功插入,`1002` 已经消失。如果 MySQL 的新增、更新、删除都能体现在
Doris 里,这条链路就是通的。
+
+## 常见坑
+
+- MySQL 没开 binlog,或者 binlog 不是 `ROW` 格式。
+- CDC 用户缺少复制相关权限。
+- `server-id` 和其他 MySQL 副本或 CDC 作业冲突。
+- 多个运行中的任务复用了同一个 `sink.label-prefix`,导致 Doris stream load 冲突。
+- 开启了删除同步,但 Doris 目标表模型不支持预期的删除行为。
+- 源表没有稳定主键,导致 Doris 自动建表和下游 upsert 结果都不稳定。
+
+## 相关文档
+
+- [MySQL CDC Source](../../connectors/source/MySQL-CDC.md)
+- [Doris Sink](../../connectors/sink/Doris.md)
+- [SeaTunnel 引擎快速开始](../locally/quick-start-seatunnel-engine.md)
diff --git a/docs/zh/introduction/about.md b/docs/zh/introduction/about.md
index 5ed6c05f31..89c2d6073c 100644
--- a/docs/zh/introduction/about.md
+++ b/docs/zh/introduction/about.md
@@ -11,6 +11,30 @@ SeaTunnel是一个多模态、超高性能、分布式的海量数据集成工
如果你是第一次接触 SeaTunnel,建议按下面路径进入文档:
+<div
+ style={{
+ display: "grid",
+ gridTemplateColumns: "repeat(auto-fit, minmax(260px, 1fr))",
+ gap: "16px",
+ margin: "16px 0 24px",
+ }}>
+ <a
+ href="../getting-started/locally/run-your-first-job"
+ style={{
+ display: "block",
+ padding: "18px",
+ border: "1px solid var(--ifm-color-emphasis-300)",
+ borderRadius: "12px",
+ textDecoration: "none",
+ color: "inherit",
+ background: "var(--ifm-background-surface-color)",
+ boxShadow: "var(--ifm-global-shadow-lw)",
+ }}>
+ <strong>跑第一个任务</strong><br/>
+ <span>先用本地 FakeSource -> FieldMapper -> Console 链路把 SeaTunnel
跑通,再继续看真实源端到目标端的场景教程。</span>
+ </a>
+</div>
+
- [快速入门总览](../getting-started/overview.md),先建立整体路径
- [SeaTunnel
引擎快速开始](../getting-started/locally/quick-start-seatunnel-engine.md),先跑通第一个本地任务
- [作业配置指南](../getting-started/job-configuration-guide.md),开始编写真实作业
@@ -87,4 +111,4 @@ SeaTunnel 丰富了<a
href="https://landscape.cncf.io/?item=app-definition-and-d
## 了解更多
-您可以参阅[快速入门](../getting-started/locally/deployment.md) 了解后续相关步骤。
+您可以参阅[跑第一个任务](../getting-started/locally/run-your-first-job.md) 了解后续相关步骤。