A minimal project for trying Apache Iceberg online with the Cloudflare R2 Data Catalog, at almost no fixed cost.
Client devices
- Mac / Windows / Linux
- GitHub Codespaces
- iPad browser + Codespaces
- DuckDB CLI
- PyIceberg script
|
| Iceberg REST Catalog API
v
Cloudflare R2 Data Catalog
|
v
Cloudflare R2 bucket
- Iceberg metadata
- Parquet data files
- Create an Iceberg table on a Cloudflare R2 bucket
- Create, append to, and read the table from PyIceberg
- Attach DuckDB to the same Iceberg REST Catalog and read from it
- Reach the same table from several machines using one
Catalog URI,Warehouse, andToken - Build a release ZIP automatically with GitHub Actions
- Run it under GitHub Codespaces, Docker, or a local venv
The setup keeps no permanently running VM.
Storage + Catalog: Cloudflare R2 + R2 Data Catalog
Compute: local PC / GitHub Codespaces / DuckDB / Python, only when needed
The Cloudflare R2 Data Catalog is a managed Apache Iceberg catalog built into an R2 bucket. It exposes the standard Iceberg REST Catalog interface, so clients such as PyIceberg and DuckDB can connect to it.
Data Catalog catalog operations, compaction, and R2 storage and operations each have a free allowance and usage-based pricing beyond it. Prices and free allowances change, so check R2 Data Catalog pricing and R2 pricing before you run anything.
- Cloudflare account
- R2 subscription
- Python 3.10+
- Node.js / npm
- Optional: Docker
- Optional: GitHub Codespaces
npx wrangler@latest login
npx wrangler@latest r2 bucket create iceberg-lab
npx wrangler@latest r2 bucket catalog enable iceberg-labNote down the values printed afterwards.
Catalog URI
Warehouse name
To do this from the dashboard instead, create a catalog on the R2 Data Catalog screen and use the Catalog URI and Warehouse name shown on its detail page.
Create an R2 API token in the Cloudflare dashboard.
What it needs to cover:
- R2 Data Catalog read/write
- R2 storage read/write
For a first lab, starting with the equivalent of Admin Read & Write is simplest. Never commit the token to a shared repository.
cp .env.example .envEdit .env.
ICEBERG_CATALOG_URI=https://...
ICEBERG_WAREHOUSE=...
ICEBERG_TOKEN=...
ICEBERG_NAMESPACE=demo
ICEBERG_TABLE=people
ICEBERG_CATALOG_NAME=r2
DUCKDB_CATALOG_ALIAS=r2_icebergpython3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"uv venv
source .venv/bin/activate
uv pip install -e ".[dev]"iceberg-r2-lab doctor
iceberg-r2-lab create
iceberg-r2-lab append --batch 1
iceberg-r2-lab read --limit 20
iceberg-r2-lab listThe same steps are available through the Makefile.
make doctor
make create
make append
make read
make listGenerate the attach SQL for DuckDB.
iceberg-r2-lab duckdb-sql > .generated/duckdb_attach.sqlThe generated SQL does not contain the token value. Export .env into the shell environment, then start DuckDB.
set -a
source .env
set +a
duckdbRun this inside DuckDB.
.read .generated/duckdb_attach.sql
SHOW ALL TABLES;
SELECT *
FROM r2_iceberg.demo.people
LIMIT 20;A template SQL file is also kept here.
examples/duckdb/attach_r2_template.sql
docker compose build
docker compose run --rm lab iceberg-r2-lab doctor
docker compose run --rm lab iceberg-r2-lab create
docker compose run --rm lab iceberg-r2-lab append
docker compose run --rm lab iceberg-r2-lab read- Upload or push this ZIP to a GitHub repo
- Open GitHub Codespaces
- Add the following to the Codespaces secrets
ICEBERG_CATALOG_URIICEBERG_WAREHOUSEICEBERG_TOKEN
- Run it in the terminal
python -m pip install -e ".[dev]"
iceberg-r2-lab doctor
iceberg-r2-lab readThe same table is reachable from an iPad too, through the Codespaces browser terminal.
Building locally:
make packageOutput:
dist/iceberg-r2-online-lab-v1.0.1.zip
dist/iceberg-r2-online-lab-v1.0.1.zip.sha256
Releasing through GitHub Actions:
VERSION="$(cat VERSION)"
git tag "v${VERSION}"
git push origin "v${VERSION}".github/workflows/release.yml attaches the ZIP and its SHA256 as release assets.
.
├── .devcontainer/
├── .github/workflows/
├── docs/
├── examples/
│ ├── duckdb/
│ ├── pandas/
│ └── pyiceberg/
├── scripts/
├── src/iceberg_r2_lab/
├── tests/
├── .env.example
├── docker-compose.yml
├── Dockerfile
├── Makefile
├── pyproject.toml
├── README.md
└── VERSION
make install
make doctor
make create
make append
make read
make list
make duckdb-sql
make test
make packageTo drop the test table:
iceberg-r2-lab drop-table --yesTo turn the catalog itself off:
npx wrangler@latest r2 bucket catalog disable iceberg-labBefore deleting the bucket, check its contents from the Cloudflare dashboard or with Wrangler.
Do not commit .env, .generated/, tokens, or credentials.
git statusConfirm that .env does not appear in the output.
- Cloudflare R2 Data Catalog: https://developers.cloudflare.com/r2/data-catalog/
- Cloudflare R2 Data Catalog management: https://developers.cloudflare.com/r2/data-catalog/manage-catalogs/
- PyIceberg: https://py.iceberg.apache.org/
- DuckDB Iceberg REST Catalogs: https://duckdb.org/docs/lts/core_extensions/iceberg/iceberg_rest_catalogs.html
- GitHub Releases: https://docs.github.com/en/repositories/releasing-projects-on-github/about-releases