- Initial snapshot of all existing rows, then continuous streaming from PostgreSQL’s logical replication
- One Iceberg table per source table, auto-created on first write
- Every change (insert, update, delete) is appended as a new row with an
_opcolumn indicating the operation type, plus_ts_msand_timestampfor event timing - Exactly-once file commits via checkpointed two-phase commit
Prerequisites
- A running Ingest Engine deployment with the controller, operator, and storage configured
- PostgreSQL with
wal_level = logicaland a user withREPLICATIONprivilege — see CDC connector prerequisites - An Iceberg catalog (REST, AWS Glue, or Hive Metastore) with a target database/namespace
- Object storage for Iceberg data files (S3, GCS, or ADLS)
PostgreSQL configuration
Ensure these settings on the PostgreSQL instance:Pipeline Deployment
An Ingest Engine CDC pipeline consists of three resources: a source (where to read changes from), a sink (where to write them), and a pipeline (the SQL query connecting them). These can be deployed in two ways:- Kubernetes Manifest — declare all three resources as YAML and apply with
kubectl. The Ingest Engine operator reconciles them and starts the pipeline. This is the recommended approach for production deployments and GitOps workflows. - REST API — create each resource individually via HTTP calls to the Ingest Engine controller API. Useful for scripting, automation, and environments where
kubectlaccess is not available.
Via Kubernetes Manifest
Create a YAML manifest with the following three resources. All three can be placed in a single file separated by--- for convenience.
Step 1: CDC Source
Define aLaminarTable resource for the CDC source. This tells Ingest Engine which PostgreSQL database to connect to, which tables to capture, and how to handle the initial snapshot.
No column schema needed for the source tables — column names and types are read from PostgreSQL automatically and carried with each batch of rows.
Step 2: Iceberg Sink
Define aLaminarTable resource for the Iceberg sink. This configures the catalog connection, target namespace, multi-table routing, storage credentials, and output format.
catalog and storageOptions sections with:
credential is the client ID and secret separated by a colon.
If your data is in a different AWS account, use client.assume-role.arn in storageOptions for cross-account S3 access.
For production deployments, avoid static credentials in the manifest. Instead, use your cloud platform’s pod identity mechanism to grant storage access:
- AWS (EKS): IAM Roles for Service Accounts (IRSA) — the pod assumes an IAM role with S3 permissions
- GCP (GKE): Workload Identity — the pod uses a GCP service account with GCS permissions
- Azure (AKS): Workload Identity — the pod uses a managed identity with ADLS permissions
storageOptions credentials can be omitted entirely — the pod inherits storage permissions from its service account. The auth block for catalog authentication is still required.
Key configuration:
Step 3: Pipeline
Define aLaminarPipeline resource that connects the CDC source to the Iceberg sink using a SQL query. The query forwards the CDC envelope from source to sink, and the sink automatically unwraps and routes each table.
Step 4: Apply Manifest
Apply the manifest to your Kubernetes cluster. The Ingest Engine operator will reconcile the resources, create the connection tables via the controller API, and start the pipeline automatically.config block inside each resource is identical to the API format — only the Kubernetes wrapper (apiVersion, kind, metadata, spec.clusterRef) differs.
Via REST API
Set the API values used by the requests:Step 1: Create the CDC source
Register the PostgreSQL CDC source by posting the connection config to the Ingest Engine API. This creates a connection table that Ingest Engine uses to connect to PostgreSQL and capture changes.Step 2: Create the Iceberg sink
Register the Iceberg sink by posting the catalog connection, namespace, multi-table routing, and storage credentials. Ingest Engine will auto-create Iceberg tables in this namespace as CDC data arrives.catalog and storageOptions sections with the Polaris-specific config shown in the Kubernetes Manifest section above.
Step 3: Create the pipeline
Create the pipeline with a SQL query that connects the CDC source to the Iceberg sink. The pipeline starts automatically once created and begins with a full snapshot of existing data.Watch the snapshot
The pipeline begins with a full snapshot of existing data. In worker logs:Verify the output
Check Iceberg tables
Tables are auto-created in the configured namespace. Verify via your catalog:Query with pyiceberg
Query with DuckDB
Test streaming
Make changes in PostgreSQL and verify they land in Iceberg:Snapshot modes
Set via
"snapshotMode": "incremental" in the CDC source config.
Multi-table setup
A single pipeline can capture any number of tables. List the tables to capture in the source config:tableNameTemplate:
Cleanup
Via Kubernetes Manifest
Via REST API
Production considerations
- Checkpoint interval: 10 seconds is a good balance. Lower values (1–3s) reduce end-to-end latency but increase Iceberg commit frequency. Higher values (30–60s) reduce commit overhead for high-throughput pipelines.
- JVM heap: 512m is sufficient for most workloads. Increase to 1–2g for databases with very wide schemas (200+ columns) or large initial snapshots.
Notes
- Source configuration, snapshot modes, and database prerequisites: CDC connector reference.