Get started
This page gets you from nothing to querying Iceberg tables and files in object storage. It takes about ten minutes with Docker.
Install pg_lake
There are two ways to set up pg_lake:
- Docker runs PostgreSQL with pg_lake, pgduck_server and S3-compatible storage (LocalStack) with one command. This is the quickest way to try pg_lake.
- Building from source installs pg_lake into an existing PostgreSQL 16, 17, 18 or 19 installation, or sets up a full development environment.
With Docker
You need Docker and Task:
git clone https://github.com/Snowflake-Labs/pg_lake.git
cd pg_lake/docker
task compose:up
The first build takes a while. Once the services are up, connect with psql:
psql -h localhost -p 5432 -U postgres
The Docker setup creates the extensions (including pg_lake_spatial), gives pgduck_server credentials for the LocalStack bucket s3://testbucket, and sets pg_lake_iceberg.default_location_prefix to s3://testbucket/pg_lake/. You can skip to creating your first Iceberg table; in the examples below, use s3://testbucket wherever they say s3://mybucket.
From source
After building and installing pg_lake:
-
Add
pg_extension_basetoshared_preload_librariesinpostgresql.conf, and restart PostgreSQL:shared_preload_libraries = 'pg_extension_base' -
Start pgduck_server, which listens on a Unix socket in
/tmpby default:pgduck_server --cache_dir /var/lib/pgduck/cache -
Make sure pgduck_server can reach your object storage. For AWS, it uses the usual credential chain, such as
~/.aws/credentialsor an instance profile; see object storage credentials for other options. -
Create the extensions, and tell pg_lake where to store Iceberg tables:
CREATE EXTENSION pg_lake CASCADE; NOTICE: installing required extension "pg_lake_table" NOTICE: installing required extension "pg_lake_engine" NOTICE: installing required extension "pg_extension_base" NOTICE: installing required extension "pg_map" NOTICE: installing required extension "pg_lake_iceberg" NOTICE: installing required extension "btree_gist" NOTICE: installing required extension "pg_lake_copy" CREATE EXTENSION ALTER DATABASE postgres SET pg_lake_iceberg.default_location_prefix TO 's3://mybucket/iceberg';The setting takes effect in new sessions, so reconnect before the next step.
Create your first Iceberg table
Add USING iceberg to CREATE TABLE:
CREATE TABLE measurements (
station text NOT NULL,
measured_at timestamptz NOT NULL,
temperature double precision
)
USING iceberg;
INSERT INTO measurements
SELECT (ARRAY['Amsterdam', 'Istanbul', 'Seattle'])[1 + i % 3],
now() - i * interval '1 minute',
10 + 15 * random()
FROM generate_series(1, 100000) i;
SELECT station, count(*), round(avg(temperature)::numeric, 1) AS avg_temp
FROM measurements
GROUP BY station
ORDER BY station;
station | count | avg_temp
-----------+-------+----------
Amsterdam | 33333 | 17.5
Istanbul | 33334 | 17.5
Seattle | 33333 | 17.5
(3 rows)
The table is stored as Parquet files and Iceberg metadata in object storage, and the aggregate ran on DuckDB. It is still a PostgreSQL table: you can update it, join it with other tables, and use it in transactions.
BEGIN;
DELETE FROM measurements WHERE temperature < 11;
UPDATE measurements SET station = 'New York' WHERE station = 'Seattle';
COMMIT;
The iceberg_tables view shows where each table’s current metadata is, so that other engines such as Spark or Snowflake can read it:
SELECT table_name, metadata_location FROM iceberg_tables;
Export and query files
COPY can write any query result to object storage, in Parquet, CSV or JSON:
COPY (SELECT * FROM measurements WHERE station = 'Istanbul')
TO 's3://mybucket/exports/istanbul.parquet';
To query files in place, create a foreign table on the pg_lake server. With an empty column list, the columns are inferred from the files:
CREATE FOREIGN TABLE istanbul () SERVER pg_lake
OPTIONS (path 's3://mybucket/exports/*.parquet');
SELECT count(*) FROM istanbul;
This works for public data too. For example, the New York City taxi trip records are published as Parquet files over HTTPS:
CREATE FOREIGN TABLE taxi_trips () SERVER pg_lake
OPTIONS (path 'https://d37ci6vzurychx.cloudfront.net/trip-data/yellow_tripdata_2024-01.parquet');
SELECT date_trunc('day', tpep_pickup_datetime) AS day, count(*), avg(total_amount)
FROM taxi_trips
GROUP BY 1 ORDER BY 1 LIMIT 5;
To keep a copy, load the file into an Iceberg table in one step:
CREATE TABLE taxi_trips_2024 () USING iceberg
WITH (load_from = 'https://d37ci6vzurychx.cloudfront.net/trip-data/yellow_tripdata_2024-01.parquet');
Next steps
- How pg_lake works explains the architecture, table types and transactions.
- Configuration covers credentials, pgduck_server options and permissions for a real deployment.
- Iceberg tables covers partitioning, updates, catalogs and maintenance.
- Use cases has end-to-end examples, such as syncing Postgres tables to Iceberg.