Olve Heitmann
09/12/2025, 9:13 PMMarvin
09/12/2025, 9:13 PMMarvin
09/12/2025, 9:18 PMfrom prefect import flow
@flow
def my_flow():
# Inside the container, refer to the mounted path
db_path = "/data/my.duckdb"
# use duckdb.connect(db_path) or your connector of choice
my_flow.from_source(
source=".", # or a repo URL
entrypoint="flows/my_flow.py:my_flow",
).deploy(
name="duckdb-etl",
work_pool_name="docker-pool",
image="your-repo/your-image:latest",
job_variables={
"volumes": [
"C:/prefect-data:/data" # Mount host folder C:\prefect-data into /data
# optionally add :ro or :rw, default is rw
],
# optional: "env": {"DUCKDB_PATH": "/data/my.duckdb"}
},
)
You can also set this once at the pool level so all deployments inherit it:
- Prefect UI -> Work Pools -> your Docker pool -> Edit Base Job Template -> add a volumes list like:
{
"volumes": [
"C:/prefect-data:/data"
]
}
Or inspect the available fields with:
prefect work-pool get-default-base-job-template --type docker
You’ll see volumes is a supported field.
2) Use a Docker named volume
- Define a named volume (e.g., prefect_duckdb) and mount it for all flow runs:
job_variables = {
"volumes": ["prefect_duckdb:/data"]
}
- This keeps data inside Docker’s VM (with Docker Desktop on Windows). It’s great if all read/write access happens from containers. It’s less convenient if you need to open the file directly on the Windows host.
Important Windows notes
- Ensure your C: drive is shared in Docker Desktop.
- Use paths like C:/path/to/dir:/data or escape backslashes C:\\path\\to\\dir:/data.
- Prefer mounting a directory, then refer to the file inside it (e.g., /data/my.duckdb).
- If your worker itself runs in Docker, it must have access to the host Docker daemon (the standard Prefect Docker worker container mounts the Docker socket). The bind mount path is resolved on the Docker host, not inside the worker container.
Concurrency and DuckDB safety
- DuckDB supports many readers but typically only one writer at a time per file. If multiple flows write to the same file, serialize writes.
- In Prefect, you can enforce this with a concurrency limit on a tag for writer tasks:
prefect concurrency-limit create duckdb-writer --limit 1
Then tag your write operations with duckdb-writer.
Docs
- Customize job variables: Customize job variables (Prefect 3.x)
If you share a bit more about where the DuckDB file lives today (host path, shared drive, etc.) and whether all writers will run as Prefect-managed Docker containers on the same machine, I can suggest the exact volume string and the cleanest setup.Olve Heitmann
09/12/2025, 9:25 PMMarvin
09/12/2025, 9:25 PMMarvin
09/12/2025, 9:26 PMC:\prefect-data\duckdb)
- Mount that directory into your flow-run containers (e.g., /data)
- Inside your flows, always reference the DB via the container path (e.g., /data/my.duckdb)
- Outside Docker, use the Windows path (e.g., C:\prefect-data\duckdb\my.duckdb)
Example deployment using a Docker work pool
my_flow.from_source(
source=".", # or your repo URL
entrypoint="flows/my_flow.py:my_flow",
).deploy(
name="duckdb-etl",
work_pool_name="docker-pool",
image="your-repo/your-image:latest",
job_variables={
"volumes": [
"C:/prefect-data/duckdb:/data"
],
# optional: "env": {"DUCKDB_PATH": "/data/my.duckdb"}
},
)
Or set it once at the pool level so all deployments inherit it:
- UI -> Work Pools -> your Docker pool -> Edit Base Job Template -> add
{
"volumes": [
"C:/prefect-data/duckdb:/data"
]
}
Notes and caveats on Windows
- Use C:/path/...:/container/path or escape backslashes C:\\path\\...:/container/path
- Ensure your C: drive is shared in Docker Desktop
- Prefer mounting the directory that contains the .duckdb file, not the file itself
- Avoid spaces in the host path to sidestep “invalid volume specification” issues
Concurrency and safety with DuckDB
- DuckDB supports many readers but typically only one writer. If multiple flows (or your host apps) might write concurrently, serialize writes.
- In Prefect, you can enforce this with a concurrency limit tag:
- prefect concurrency-limit create duckdb-writer --limit 1
- Tag writer tasks with duckdb-writer
- If you also write from outside Prefect, try to avoid overlapping with container writes, or implement retry logic on “database is locked” errors.
Why not a Docker named volume?
- Named volumes live inside Docker Desktop’s VM, which makes host access awkward. Since you need regular host access, bind-mounting a Windows directory is the right choice.
Docs
- Customize job variables (including volumes): Customize job variables (Prefect 3.x)
If you share the exact host path you plan to use and your pool name, I can give you a ready-to-paste job_variables block and confirm the flow code path references.Olve Heitmann
09/12/2025, 9:43 PMMarvin
09/12/2025, 9:43 PMMarvin
09/12/2025, 9:45 PMC:/...:/data becomes three parts (C, /..., /data). The parser thinks /data is the “mode” and errors because mode must be ro or rw.
Two solid fixes (pick one)
A) Use the Docker Desktop Linux host path form
- On Windows with Docker Desktop (Linux engine), host drives are exposed under /host_mnt/<drive-letter>/....
- Use that Linux-style path and include an explicit mode, e.g. :rw.
Example:
job_variables = {
"volumes": [
"/host_mnt/c/qfin/qfin/database:/data:rw"
],
"env": {
"QFIN_DATABASE_DUCK": "/data/database.duckdb"
},
}
Notes:
- Ensure your C: drive is shared in Docker Desktop.
- Inside your flows, use /data/database.duckdb.
- Outside Docker, use C:\qfin\qfin\database\database.duckdb.
B) Mount the parent directory instead of a single file
- This avoids edge cases and gives you flexibility if you add more files later.
- Same syntax as above, just point to the directory (as you already do).
Why not keep the Windows C:/... format?
- The worker’s Linux-side parser will always misread the C: colon as a separator.
- Using /host_mnt/c/... sidesteps that and aligns with the Linux Docker engine’s view of your Windows filesystem.
Optional: set once at the pool level
- Prefect UI -> Work Pools -> your Docker pool -> Edit Base Job Template -> add:
{
"volumes": [
"/host_mnt/c/qfin/qfin/database:/data:rw"
]
}
Docs
- Customize job variables: Customize job variables (Prefect 3.x)
If you try the /host_mnt/... form and still see issues, let me know your exact pool name and the full job_variables you’re using; I’ll tailor a ready-to-paste block.Olve Heitmann
09/12/2025, 10:00 PMMarvin
09/12/2025, 10:00 PMMarvin
09/12/2025, 10:05 PMmy_flow.from_source(
source="<https://github.com/OWNER/REPO>", # or your git server URL
entrypoint="path/to/flow_file.py:my_flow",
).deploy(
name="duckdb-etl",
work_pool_name="docker-pool",
image="your-repo/your-image:latest",
job_variables={
"volumes": ["/host_mnt/c/qfin/qfin/database:/data:rw"],
"env": {"QFIN_DATABASE_DUCK": "/data/database.duckdb"},
},
)
- If the repo is private, add git credentials via job_variables["env"] or your image.
Option B: Bundle your code into the image (no Windows path recorded)
- Build an image that copies your project into the image and sets a Linux workdir (e.g., /app), then use that image in your deployment. Example Dockerfile:
FROM python:3.12-slim
WORKDIR /app
COPY . /app
RUN pip install -U pip && pip install -U prefect duckdb <other deps>
- Build and push the image, then deploy referencing that image (and do not point Prefect at a local Windows path for source):
my_flow.deploy(
name="duckdb-etl",
work_pool_name="docker-pool",
image="your-repo/your-image:latest",
job_variables={
"volumes": ["/host_mnt/c/qfin/qfin/database:/data:rw"],
"env": {"QFIN_DATABASE_DUCK": "/data/database.duckdb"},
# optional: "container_create_kwargs": {"working_dir": "/app"}
},
)
- This avoids the “pull from local Windows path” step entirely because the code is already in the image.
Volume reminder for Windows + Docker Desktop
- Keep using the Linux-style path and explicit mode:
- "/host_mnt/c/qfin/qfin/database:/data:rw"
- Inside your flow, read/write /data/database.duckdb.
- On your host, use C:\qfin\qfin\database\database.duckdb.
After you switch to A or B, re-create the deployment so the old Windows path isn’t embedded in its steps.
Reference
- Customize job variables (e.g., volumes): Customize job variables (Prefect 3.x)
If you share your current deployment code (the exact from_source(...).deploy(...) snippet), I can point out the minimal change needed and provide a ready-to-paste version.Olve Heitmann
09/12/2025, 10:15 PMMarvin
09/12/2025, 10:16 PMMarvin
09/12/2025, 10:17 PMflow.from_source(... ) needs to run git clone (via an async subprocess) to pull your repo locally.
- On Windows, the default asyncio event loop policy may not support subprocesses, leading to NotImplementedError when create_subprocess_exec is called.
Quickest fix on Windows
- Set the asyncio loop policy to one that supports subprocesses before you call from_source. Add this at the very top of the script where you’re calling `from_source`:
import sys
import asyncio
if sys.platform == "win32":
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
Then your deployment code can run as-is:
from prefect import flow
@flow
def my_flow():
...
my_flow.from_source(
source="<https://github.com/OWNER/REPO>",
entrypoint="path/to/flow.py:my_flow",
).deploy(
name="duckdb-etl",
work_pool_name="docker-pool",
image="your-repo/your-image:latest",
job_variables={
"volumes": ["/host_mnt/c/qfin/qfin/database:/data:rw"],
"env": {"QFIN_DATABASE_DUCK": "/data/database.duckdb"},
},
)
Notes
- Make sure git is installed and on your PATH in the Windows environment running the deploy.
- Using uv as a package manager should not affect this; the event loop policy is the key here.
- Alternatively, run the deploy command from WSL/Linux (where subprocess support is native), or use the “code in image” pattern (bundle your flow code into the image and call flow.deploy(...) without from_source).
You’re already on the right track with the volume:
- Inside containers: use /data/database.duckdb
- On Windows host: C:\qfin\qfin\database\database.duckdb
- Keep using "/host_mnt/c/qfin/qfin/database:/data:rw" for the mount.
If you still see issues after setting the loop policy, share your deploy script snippet (top ~15 lines and the from_source(...).deploy(...) call) and I’ll tailor a ready-to-paste version.Olve Heitmann
09/12/2025, 10:20 PMMarvin
09/12/2025, 10:20 PMMarvin
09/12/2025, 10:22 PMFROM python:3.12-slim
WORKDIR /app
COPY . /app
RUN pip install -U pip && pip install -U prefect duckdb # + your deps
- Deploy script (run on your Windows host)
from prefect import flow
from flows.my_flow import my_flow # adjust to your project structure
# Important: do NOT use from_source here
my_flow.deploy(
name="duckdb-etl", # new name to avoid stale config
work_pool_name="docker-pool",
image="your-repo/your-image:latest", # already built/pushed image
build=False,
push=False,
job_variables={
"container_create_kwargs": {"working_dir": "/app"},
"volumes": ["/host_mnt/c/qfin/qfin/database:/data:rw"],
"env": {"QFIN_DATABASE_DUCK": "/data/database.duckdb"},
},
)
Key checks
- In the Prefect UI, open the deployment and inspect the “Pull steps”. For a “code in image” deployment, you should NOT see a “pull from storage” step. You should see a “load from entrypoint” step. If you still see storage steps, delete the deployment and recreate it with the code above.
- Ensure that the flow file path inside the image matches the entrypoint Prefect records. With the example above, Prefect will record something like flows/my_flow.py:my_flow. That file must exist at /app/flows/my_flow.py in the image (we set working_dir to /app).
Windows + volume reminder
- Keep using Linux-style path + explicit mode:
- "/host_mnt/c/qfin/qfin/database/datarw"
- Inside container: use /data/database.duckdb
- On host: C:\qfin\qfin\database\database.duckdb
Optional
- Upgrade Prefect to the latest 3.x on your dev machine and in the image to avoid older storage/pull-step quirks:
- uv add -U prefect
Docs
- Customize job variables (e.g., volumes, container_create_kwargs): Customize job variables (Prefect 3.x)
If you can share:
- Your current Dockerfile
- The exact deploy snippet you used
I can confirm the entrypoint and provide a ready-to-paste corrected deployment.