Set up a DuckLake catalog on PostgreSQL with DuckDB, then read its Parquet data from Firebolt using READ_DUCKLAKE and LIST_DUCKLAKE_FILES.
DuckLake support is experimental and may change. Only DuckLake catalogs hosted on PostgreSQL are supported.
This guide walks you through setting up a DuckLake catalog on PostgreSQL with Parquet data files on local disk, and then reading that data from Firebolt. You’ll create a table with DuckDB and query it from Firebolt using READ_DUCKLAKE and LIST_DUCKLAKE_FILES.
The remaining commands in this step run inside the DuckDB shell.
Install and load the DuckLake extension:
INSTALL ducklake;LOAD ducklake;
Create the DuckLake catalog in PostgreSQL and attach it. Use the same credentials you set for the PostgreSQL container. The DATA_PATH option determines where the Parquet data files are written on local disk:
-- Use the DuckLake catalogUSE pg_ducklake;-- Create a table in the DuckLake catalogCREATE TABLE my_first_ducklake_table (a INT, r FLOAT);-- Insert sample dataINSERT INTO my_first_ducklake_table SELECT x, random() FROM generate_series(1, 10000) g(x);
Confirm the table exists, with its Parquet files written to local disk:
.timer on -- optional: show query latencySELECT * FROM my_first_ducklake_table ORDER BY r DESC LIMIT 5;
Start the Firebolt container. Two options matter for DuckLake:
--network host lets the Firebolt binary inside the container reach the PostgreSQL container.
-v /tmp/ducklake:/tmp/ducklake mounts the local directory where DuckDB wrote the Parquet files into the Firebolt container at the same path.
The --ulimit memlock and --security-opt seccomp=unconfined flags let Firebolt use io_uring. Without them Firebolt still starts, but queries that need io_uring, including queries that spill to disk, fail. See execution.io_uring.
The container from Step 3 runs in the foreground, so open a new terminal to connect to Firebolt. Any supported client works — connect to the query endpoint on port 3473, then run the SQL below.
Create a location object that points to the DuckLake catalog. Use the same credentials you set for the PostgreSQL container. Because the data files are on local disk, no endpoint or storage credentials are needed:
CREATE LOCATION my_ducklake_loc WITH SOURCE = DUCKLAKE CATALOG = 'postgresql://dl_user:dl_pw@127.0.0.1:5432/dl_db';
SELECT *FROM LIST_DUCKLAKE_FILES( LOCATION => 'my_ducklake_loc', SCHEMA => 'main', -- optional: 'main' is the default TABLE => 'my_first_ducklake_table');
Read the data, the same query you ran in DuckDB:
SELECT *FROM READ_DUCKLAKE( LOCATION => 'my_ducklake_loc', TABLE => 'my_first_ducklake_table')ORDER BY r DESCLIMIT 5;
Inspect the query plan to see how caching behaves across repeated runs. Run it twice and compare the two plans, the second run reads cached metadata and data:
EXPLAIN (ANALYZE)SELECT *FROM READ_DUCKLAKE( LOCATION => 'my_ducklake_loc', TABLE => 'my_first_ducklake_table')ORDER BY r DESCLIMIT 5;