Overview
DuckDB is an in-process analytical SQL engine that reads and writes Parquet, CSV and JSON directly, queries object storage over the S3 API, and can attach live PostgreSQL, MySQL and SQLite databases. This template runs a SQL script of yours on a cron schedule and exits. The read path overlaps Trino: both query Parquet in object storage and join across attached databases. The difference is cost shape. Trino keeps a coordinator and workers running so a query can arrive at any moment; DuckDB here consumes nothing between runs and bills only for the minutes its job is executing. Pick this template when the work is a known transform on a known schedule, and Trino when a human or a dashboard needs to ask ad-hoc questions.What Gets Created
- Cron Workload —
{release}-duckdb, running the officialduckdb/duckdbimage once per schedule. No ports, no load balancer,internal.inboundAllowType: noneand an empty inbound CIDR list. Outbound is open. - Preamble Secret —
{release}-duckdb-preamble, an opaque secret ofSETstatements mounted at/etc/duckdb/preamble.sqland executed before your script. - Script Secret (optional) —
{release}-duckdb-script, holdingsql.inline, mounted at/etc/duckdb/job.sql. Not created at all when you supply your own secret throughsql.secretName. - Identity & Policy —
{release}-duckdb-identitywith a policy grantingrevealon exactly the secrets this release mounts — the preamble, the script, yoursecretEnvsecrets and your object-store credential secret. WhenobjectStore.type: aws, the identity also carries the cloud-account binding. - No Volume Sets — no volume is attached. See Memory and Storage.
This template does not create a GVC. You must deploy it into an existing GVC.
Prerequisites
- None for a default install. The shipped
sql.inlineis a self-test that needs no credentials and no cloud account. - Outbound access to
extensions.duckdb.org:443on every run. The template’s firewall allows all outbound traffic, so this works out of the box unless your organization restricts egress. See Extensions. - For
objectStore.type: aws— an AWS bucket, a Control Plane cloud account, and a bucket-scoped IAM policy. See Object Storage. - For
objectStore.type: s3-compatible— a reachable S3-compatible endpoint and a dictionary secret holding its access keys, created before install. - For
sql.secretName— an opaque secret containing your SQL, created before install. - For
secretEnv[]— every referenced secret must exist before install.
Installation
Install the template using your preferred method:UI
Browse, install, and manage templates visually
CLI
Manage templates from your terminal
Terraform
Declare templates in your Terraform configurations
Pulumi
Declare templates in your Pulumi programs
Configuration
The defaultvalues.yaml for this template:
Schedule
image— The DuckDB container image. The chart is shipped and tested on DuckDB 1.5.5.schedule— A five-field cron expression interpreted in UTC. Always quote it; the chart rejects anything that is not exactly five fields.suspend—trueinstalls the job without ever running it automatically. Trigger it by hand withcpln workload cron start.activeDeadlineSeconds— Upper bound on a single run’s duration.
concurrencyPolicy: Forbid) and a failed run is not retried (restartPolicy: Never) — the next scheduled run simply starts as normal.
SQL Script
Your script comes from one of two places, andsql.secretName wins when both are set:
With
sql.secretName set, the template’s own script secret is not created at all and the workload mounts your secret directly:
.bail on and the derived memory_limit, threads, temp_directory and extension_directory settings. Because the preamble runs first, a SET in your own script always wins.
Credentials in SQL
secretEnv[] turns Control Plane secrets into container environment variables, readable from your SQL with getenv('NAME'). The value never appears in your values file or in the rendered workload spec — only a cpln://secret/... reference does.
AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN and AWS_DEFAULT_REGION are rejected — object-store credentials are owned by objectStore, and setting them here would silently override it.
Instead, name the secretEnv entry after the database driver’s own password variable and leave the password out of the connection string entirely — the driver reads it from the environment:
getenv('NAME') still works anywhere an ordinary expression is allowed — a WHERE clause, a computed column, a COPY destination built with ||. The restriction is specific to ATTACH. If a credential truly must sit inline, put the whole connection string into a sql.secretName secret instead.
Templates deployed in the same GVC are reachable at {workload-name}.{gvc}.cpln.local — for example postgres on 5432 or mysql on 3306.
Resources and Tuning
DuckDB reads the host machine’s RAM and core count rather than the container’s limits, so left alone it would size itself for hardware it does not have and get OOM-killed. The template therefore derives both settings from your values and writes them into the preamble explicitly:tuning.memoryLimitPercent must be between 20 and 80. Above 80 is the default DuckDB behavior that gets containers killed; below 20 wastes the container.
The chart validates the resource block at render time and refuses to install with a message naming the value to fix: minMemory may not exceed maxMemory, minCpu may not exceed maxCpu, and maxCpu:minCpu may not exceed 4:1 (a Control Plane limit).
Memory and Storage
No volume is attached to this workload, which has two consequences worth planning around. Every job must fit in memory. Size the work withresources.maxMemory and tuning.memoryLimitPercent rather than relying on spill. DuckDB’s out-of-core operators do still function, but they spill to container-local scratch at /tmp/duckdb-temp bounded by container disk — not to a sized, persistent volume. Larger-than-memory processing is not a capability this template offers.
No .duckdb database file is kept either. Every run starts from an empty in-memory database, so results must be written somewhere durable: object storage, or a table in an attached database.
Extensions
DuckDB extensions are written to/tmp/duckdb-extensions, which is container-local scratch. Because there is no cache volume, extensions are re-downloaded from extensions.duckdb.org on every single run. This is a real per-run dependency: a job that runs fine today will fail if egress to that host is later blocked.
The commonly used httpfs and aws extensions autoload on first use of an s3:// path, so most scripts never issue an explicit INSTALL.
Object Storage
objectStore.type decides whether the preamble registers a DuckDB S3 secret. Anything other than none lets your SQL read and write s3:// paths directly.
- None (default)
- AWS S3 (keyless)
- S3-compatible
No S3 secret is registered. The job can still read local files, HTTP URLs and attached databases.
Connecting
This template exposes nothing to connect to — it is a job, not a server. Observe and drive it instead:Confirming a Run Succeeded
A run succeeded only when both of these are true:- The line
duckdb-job-completeappears in the job’s log output. - The run’s status is
Successfulincpln workload cron get.
- The marker alone is not enough. A run terminated for exceeding
activeDeadlineSecondscan finish its script inside the termination lag and print the marker on its way out, while the platform has already recorded the run as failed. - The status alone is not enough. DuckDB’s CLI can exit
0on some failed scripts (upstream issue #16574), which would leave the run looking successful.
.bail on stops execution at the first error — so a SQL error aborts the script and the marker is never printed. Alerting on the pair is what makes the signal trustworthy in both directions.
Important Notes
- This is a batch job, not a query service. Nothing is listening after install; that is correct behavior. For an always-on SQL endpoint, use trino.
- Check both success signals — the
duckdb-job-completemarker and a run status ofSuccessful. Neither one alone is reliable. activeDeadlineSecondsterminates a run within roughly two minutes of the deadline, not exactly at it, and the container bills for that window.- Every job must fit in memory. No volume is attached, so raise
resources.maxMemoryrather than relying on spill. - Never
SET memory_limithigher than the container. Changetuning.memoryLimitPercentinstead — DuckDB left to itself targets 80% of the host machine and gets OOM-killed. - No
.duckdbfile is kept. Every run starts from an empty in-memory database; write results to object storage or an attached database. - Extensions are re-downloaded on every run from
extensions.duckdb.org. Every execution depends on that host being reachable. - Prerequisite secrets must exist before install, and uninstalling the release does not delete them — the template only removes the secrets it created itself.
- Installing this template several times is scale-out, not high availability. Separate releases with different scripts or schedules run independently, but there is no failover: if tonight’s container dies, tonight’s job did not happen.
- One script per install, by design. Multi-step, conditional or retrying pipelines belong in airflow.
External References
DuckDB Documentation
Official DuckDB documentation
CLI Arguments
The CLI flags this template uses to run your script
Configuration Reference
Every setting available to a preamble or script
S3 API Support
Reading and writing s3:// paths with httpfs
Tuning Workloads
How memory and thread settings affect performance
DuckDB Template
View the source files, default values, and chart definition