oleander-whirlwind
10/08/2025, 12:02 PMMarvin
10/08/2025, 12:02 PMMarvin
10/08/2025, 12:06 PMpip install prefect prefect-gitlab
- For a public repo, you can pass a Git URL directly
- For a private repo, use the prefect-gitlab integration with a token
Public repo example:
from prefect.flows import Flow
# Repo is public; Prefect will clone it when running
flow = Flow.from_source(
source="<https://gitlab.com/<group>/<project>.git>",
entrypoint="flows/etl.py:etl",
)
# Deploy to a work pool; optionally build/push a container image
flow.deploy(
name="etl-prod",
work_pool_name="k8s-pool", # or docker/ecs/etc.
# image="<http://registry.gitlab.com/<group>/<project>/prefect:latest|registry.gitlab.com/<group>/<project>/prefect:latest>",
# build=True,
# push=True,
)
Private repo example (using prefect-gitlab):
import os
from prefect.flows import Flow
from prefect_gitlab.repositories import GitLabRepository
from prefect_gitlab.credentials import GitLabCredentials
repo = GitLabRepository(
repository="<group>/<project>", # GitLab path, e.g. "my-group/my-repo"
reference="main", # branch, tag, or commit SHA (optional)
path="", # subdirectory within repo (optional)
host="<http://gitlab.com|gitlab.com>", # or your self-managed host
credentials=GitLabCredentials(token=os.environ["GITLAB_TOKEN"]),
)
flow = Flow.from_source(
source=repo,
entrypoint="flows/etl.py:etl", # path/to/file.py:flow_function_name
)
flow.deploy(
name="etl-prod",
work_pool_name="k8s-pool",
# image="<http://registry.gitlab.com/<group>/<project>/prefect:latest|registry.gitlab.com/<group>/<project>/prefect:latest>",
# build=True,
# push=True,
)
Notes:
- You’ll need a GitLab Personal Access Token with at least read_repository for private repos.
- If you’re building/pushing a Docker image, your token/CI will also need registry permissions.
2) Deploy via CLI in GitLab CI
If you prefer CLI, run prefect deploy from your GitLab pipeline. This is great when your repo is already checked out by CI.
Minimal (no image build) — good for serverless/Kubernetes workers:
# .gitlab-ci.yml
image: python:3.11
stages: [deploy]
deploy:
stage: deploy
variables:
PREFECT_API_URL: $PREFECT_API_URL
PREFECT_API_KEY: $PREFECT_API_KEY
before_script:
- pip install -U prefect prefect-gitlab
- prefect version
# Optional: create the work pool once (safe to run idempotently)
- prefect work-pool create "$WORK_POOL" --type kubernetes || true
script:
# Run from the project root so entrypoint paths resolve
- prefect deploy flows/etl.py:etl --name etl-prod --work-pool "$WORK_POOL"
With image build + push to GitLab Container Registry:
- Requires Docker-in-Docker or another builder. Below uses DinD.
```
# .gitlab-ci.yml
image: docker:24.0
services:
- docker:24.0-dind
variables:
DOCKER_HOST: tcp://docker:2375
DOCKER_TLS_CERTDIR: ""
PREFECT_API_URL: $PREFECT_API_URL
PREFECT_API_KEY: $PREFECT_API_KEY
stages: [deploy]
deploy:
stage: deploy
before_script:
- apk add --no-cache py3-pip git
- pip install -U prefect prefect-gitlab
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
# Optional: create the work pool once (docker/k8s/etc.)
- prefect work-pool create "$WORK_POOL" --type docker || true
script:
# Option A: Use a small deploy.py to control image/build/push flags
- python - << 'PY'Marvin
10/08/2025, 12:06 PMWhy deploy.py? It uses the `Flow.deploy(..., image=..., build=True, push=True)` API which is explicit and version-stable. It also makes it easy to customize per environment without relying on CLI flags.
Environment variables you’ll typically set in GitLab CI/CD settings:
- PREFECT_API_URL, PREFECT_API_KEY (Prefect Cloud/server)
- WORK_POOL (e.g., `k8s-pool` or `docker-pool`)
- GITLAB_TOKEN (for private repos; scope `read_repository`)
- CI_REGISTRY_USER / CI_REGISTRY_PASSWORD (for registry auth; GitLab provides these)
- CI_REGISTRY_IMAGE (GitLab-provided, e.g., <http://registry.gitlab.com/group/project|registry.gitlab.com/group/project>)
One-time setup (if needed)
- Create a work pool (Docker, Kubernetes, etc.). You can do it once locally or in CI:
prefect work-pool create "k8s-pool" --type kubernetes
# or
prefect work-pool create "docker-pool" --type docker
```
Common gotchas
- Token scopes: for cloning private repos use `read_repository`; for pushing images use write_registry.
- Docker builds: ensure DinD or your org’s builder is available; Prefect’s build step talks to your Docker daemon.
- Entrypoint path: use repo-relative path/to/file.py:flow_func_name.
- Self-managed GitLab: set host="your.gitlab.host" in GitLabRepository.
Would you like me to tailor this to:
- public vs. private repo?
- your worker type (Docker, Kubernetes, ECS, etc.)?
- using CLI flags vs. a deploy.py script?
- building images or running from prebuilt ones?oleander-whirlwind
10/08/2025, 12:22 PMMarvin
10/08/2025, 12:23 PMMarvin
10/08/2025, 12:24 PMfrom prefect.flows import Flow
flow = Flow.from_source(
source="<https://gitlab.com/<group>/<subgroup>/<project>.git>",
entrypoint="flows/etl.py:etl",
)
2) If the repo is private, use the GitLab integration and a token with read_repository
- Do not pass just “myrepository.git”. Provide the namespace path (no .git) and host.
import os
from prefect.flows import Flow
from prefect_gitlab.repositories import GitLabRepository
from prefect_gitlab.credentials import GitLabCredentials
repo = GitLabRepository(
repository="my-group/my-subgroup/my-project", # full path, no .git
reference="main", # optional
host="<http://gitlab.com|gitlab.com>", # or your self-managed host
credentials=GitLabCredentials(token=os.environ["GITLAB_TOKEN"]),
)
flow = Flow.from_source(
source=repo,
entrypoint="flows/etl.py:etl",
)
3) Verify the environment that’s doing the clone has the token
- If the clone happens at runtime (worker pulls code), the token must be available in the worker’s environment (via work pool base job template, Kubernetes/Docker secrets, etc.).
- If you’re building an image during deploy (build=True), the token must be available in the CI environment that runs deploy.
4) Sanity test the exact URL and token outside Prefect (from the same environment)
- Public:
git ls-remote <https://gitlab.com/<group>/<subgroup>/<project>.git>
- Private:
git ls-remote <https://oauth2:${GITLAB_TOKEN}@gitlab.com/<group>/<subgroup>/<project>.git>
If this fails, it’s either the path, the host, or the token.
Common pitfalls
- Using the project’s display name instead of the path. Use the path shown under “Project information -> Details” (e.g., group/subgroup/project).
- Including “.git” inside GitLabRepository(repository="..."). Pass the path without .git; Prefect appends it.
- Self-managed GitLab not specifying host. Set host="your.gitlab.company".
- Missing token or wrong scope (needs at least read_repository). GitLab will return 404 for private repos without valid auth.
Docs: Prefect GitLab integration
If you can share:
- How you’re creating the deployment (code/CLI snippet)
- Whether the repo is private and your GitLab host
- Where the error occurs (during deploy build or at flow run time)
…I can pinpoint the exact fix.oleander-whirlwind
10/08/2025, 1:09 PMMarvin
10/08/2025, 1:09 PMMarvin
10/08/2025, 1:10 PM"myrepository.git" without the full GitLab namespace/host, or without using your token at clone time.
What usually causes that
- Repository path missing namespace: needs group/subgroup/project (not just myrepository.git)
- Using .git in the GitLabRepository(repository=...) field — pass the path without .git
- Host mismatch: not setting host="<http://gitlab.com|gitlab.com>" (or your self-managed host)
- Token not present in the environment where the clone actually happens (the worker or the build step), even if it works on your laptop
Fixes
If you’re using Flow.from_source with a Git URL:
from prefect.flows import Flow
flow = Flow.from_source(
source="<https://gitlab.com/<group>/<subgroup>/<project>.git>",
entrypoint="flows/etl.py:etl",
)
If you’re using the GitLab integration for a private repo:
import os
from prefect.flows import Flow
from prefect_gitlab.repositories import GitLabRepository
from prefect_gitlab.credentials import GitLabCredentials
repo = GitLabRepository(
repository="<group>/<subgroup>/<project>", # full namespace, no .git
reference="main", # branch/tag/SHA (optional)
host="<http://gitlab.com|gitlab.com>", # or your self-managed host
credentials=GitLabCredentials(token=os.environ["GITLAB_TOKEN"]),
)
flow = Flow.from_source(
source=repo,
entrypoint="flows/etl.py:etl",
)
Make sure the token is available wherever the clone happens
- During deployment image build (build=True): token must be set in CI
- At flow runtime: token must be set in the worker environment (K8s secret, Docker env, etc.)
Quick diagnostics
- Turn on logging and Git verbosity where the failure occurs (CI/worker):
export PREFECT_LOGGING_LEVEL=DEBUG
export GIT_TRACE=1
export GIT_CURL_VERBOSE=1
This will show the exact remote URL Prefect is trying to clone — if you see "myrepository.git" or a host mismatch, that’s the culprit.
Docs: Prefect GitLab integration
If you can share:
- The snippet you use to create the deployment (Flow.from_source or prefect deploy command)
- Whether the repo is private and your GitLab host
- Where the error happens (during image build or at runtime)
…I can pinpoint the exact change.oleander-whirlwind
10/08/2025, 1:30 PMMarvin
10/08/2025, 1:30 PMMarvin
10/08/2025, 1:31 PMbuilt-in: git clone uvd/uvd-workflow-orchestrator-example-prefect -b main --depth 1 /tmp/tmpcklg8bmrprefect
Prefect is trying to clone a remote named uvd/uvd-workflow-orchestrator-example-prefect without a host or protocol. Git interprets that like a local path and fails. Your PAT works (you proved with git ls-remote), but Prefect isn’t being told the full remote URL.
Fix: provide a full Git URL or use the GitLab integration with host + credentials.
Option A — Public repo via full URL
from prefect.flows import Flow
flow = Flow.from_source(
source="<https://gitlab.com/uvd/uvd-workflow-orchestrator-example-prefect.git>",
entrypoint="flows/etl.py:etl",
)
Option B — Private repo via prefect-gitlab (recommended)
import os
from prefect.flows import Flow
from prefect_gitlab.repositories import GitLabRepository
from prefect_gitlab.credentials import GitLabCredentials
repo = GitLabRepository(
repository="uvd/uvd-workflow-orchestrator-example-prefect", # full namespace, no .git
reference="main", # optional
host="<http://gitlab.com|gitlab.com>", # or your self-managed host
credentials=GitLabCredentials(token=os.environ["GITLAB_TOKEN"]),
)
flow = Flow.from_source(
source=repo,
entrypoint="flows/etl.py:etl",
)
Then deploy as usual:
flow.deploy(
name="etl-prod",
work_pool_name="your-pool",
# image="<http://registry.gitlab.com/uvd/uvd-workflow-orchestrator-example-prefect:prefect-latest|registry.gitlab.com/uvd/uvd-workflow-orchestrator-example-prefect:prefect-latest>",
# build=True,
# push=True,
)
Important notes
- For GitLabRepository, pass repository="group/subgroup/project" without .git, and set host="<http://gitlab.com|gitlab.com>" (or your self-managed host).
- Make sure GITLAB_TOKEN is available in the environment where the clone happens (build-time in CI if you build an image, or at runtime in the worker environment).
- If you prefer embedding the token in a URL (less secure), you can use:
source=f"<https://oauth2:{os.environ['GITLAB_TOKEN']}@gitlab.com/uvd/uvd-workflow-orchestrator-example-prefect.git>"
To confirm what Prefect is trying to clone, enable verbose logs:
export PREFECT_LOGGING_LEVEL=DEBUG
export GIT_TRACE=1
export GIT_CURL_VERBOSE=1
This will print the exact remote. You should see a proper https URL once the above changes are in place.
Docs: Prefect GitLab integration
If you can share the snippet you’re using to construct Flow.from_source(...) or the deployment code/CLI you ran, I can point to the exact line to change.oleander-whirlwind
10/08/2025, 1:39 PMMarvin
10/08/2025, 1:39 PMMarvin
10/08/2025, 1:40 PMimport os
from prefect.flows import Flow
git_url = f"<https://oauth2:{os.environ['GITLAB_TOKEN']}@gitlab.com/uvd/uvd-workflow-orchestrator-example-prefect.git>"
# Optional: sanity check the URL in the same environment
import subprocess
subprocess.run(["git", "ls-remote", git_url], check=True)
flow = Flow.from_source(
source=git_url,
entrypoint="workflows/workflow_with_non_and_decorated_task.py:my_flow",
)
A few important notes:
- Do not include Slack-style brackets or pipes in code. This is wrong:
"<https://oauth2:${GITLAB_TOKEN}@gitlab.com/...|...>"
- You can safely use classmethod form; prefer Flow.from_source(...) over flow.from_source(...) to avoid variable shadowing.
- In your traceback, you passed source=repo but you showed a string URL above—make sure you pass the variable that actually holds the URL, e.g., source=git_url.
Recommended approach for private GitLab repos (no token in URL):
import os
from prefect.flows import Flow
from prefect_gitlab.repositories import GitLabRepository
from prefect_gitlab.credentials import GitLabCredentials
assert os.getenv("GITLAB_TOKEN"), "GITLAB_TOKEN is not set"
repo = GitLabRepository(
repository="uvd/uvd-workflow-orchestrator-example-prefect", # full namespace, no .git
reference="main", # optional
host="<http://gitlab.com|gitlab.com>", # or your self-managed host
credentials=GitLabCredentials(token=os.environ["GITLAB_TOKEN"]),
)
flow = Flow.from_source(
source=repo,
entrypoint="workflows/workflow_with_non_and_decorated_task.py:my_flow",
)
If it still fails, turn on debug and confirm the exact remote Prefect is cloning:
export PREFECT_LOGGING_LEVEL=DEBUG
export GIT_TRACE=1
export GIT_CURL_VERBOSE=1
You should see a full https URL; if you see uvd/uvd-workflow-orchestrator-example-prefect without a host, that’s the problem.
Docs for reference: Prefect GitLab integration
If you paste your current Flow.from_source(...) snippet (exact values minus the token), I’ll pinpoint the fix.oleander-whirlwind
10/08/2025, 1:46 PMMarvin
10/08/2025, 1:46 PMMarvin
10/08/2025, 1:48 PMimport os, pathlib
for k in ["SSL_CERT_FILE", "REQUESTS_CA_BUNDLE", "GIT_SSL_CAINFO"]:
v = os.environ.get(k)
print(k, "=", v, "exists?", pathlib.Path(v).is_file() if v else None)
If any var points to a missing file, unset it or fix the path.
2) Sanity test from the SAME environment
export GIT_TRACE=1
export GIT_CURL_VERBOSE=1
git ls-remote <https://gitlab.com/uvd/uvd-workflow-orchestrator-example-prefect.git>
# If private, use your token:
git ls-remote <https://oauth2:${GITLAB_TOKEN}@gitlab.com/uvd/uvd-workflow-orchestrator-example-prefect.git>
If this fails with the same CA error, it’s an environment cert problem (not Prefect).
Fixes depending on where you run
A) Local machine
- If you have a custom corporate CA, install it into your OS trust store:
- Debian/Ubuntu:
sudo cp company.crt /usr/local/share/ca-certificates/company.crt
sudo update-ca-certificates
- Alpine:
sudo apk add --no-cache ca-certificates
sudo cp company.crt /usr/local/share/ca-certificates/company.crt
sudo update-ca-certificates
- RHEL/CentOS:
sudo cp company.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust
- Or point Git to the bundle explicitly (ensure the file exists):
git config --global http.sslCAInfo /etc/ssl/certs/ca-certificates.crt
# RHEL path often: /etc/pki/tls/certs/ca-bundle.crt
B) Docker image used for deploy or worker
- Install certs and add custom CA in your image:
# Debian/Ubuntu base
RUN apt-get update && apt-get install -y ca-certificates && update-ca-certificates
# Alpine base
RUN apk add --no-cache ca-certificates && update-ca-certificates
# Add your custom CA if needed
COPY company.crt /usr/local/share/ca-certificates/company.crt
RUN update-ca-certificates
- If you must point Git at a non-default bundle, set env vars:
ENV GIT_SSL_CAINFO=/etc/ssl/certs/ca-certificates.crt
ENV SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
C) Kubernetes worker
- Mount your CA as a Secret/ConfigMap into the pod and set env vars to that path.
- In Prefect 3, you can inject env via job variables on the deployment:
flow.deploy(
name="etl-prod",
work_pool_name="k8s-pool",
job_variables={
"env": {
"GIT_SSL_CAINFO": "/etc/ssl/certs/ca-certificates.crt",
"SSL_CERT_FILE": "/etc/ssl/certs/ca-certificates.crt",
}
},
)
Then ensure that path exists in the pod (either from base image certs or your mounted CA).
Temporary test only (not for production)
export GIT_SSL_NO_VERIFY=true
git ls-remote <https://gitlab.com/uvd/uvd-workflow-orchestrator-example-prefect.git>
If this makes it work, it confirms the problem is CA trust. Re-enable verification after you fix the CA bundle.
Once the CA is fixed, use a clean URL in your code (no Slack formatting):
from prefect.flows import Flow
import os
git_url = f"<https://oauth2:{os.environ['GITLAB_TOKEN']}@gitlab.com/uvd/uvd-workflow-orchestrator-example-prefect.git>"
flow = Flow.from_source(
source=git_url, # or use GitLabRepository with token
entrypoint="workflows/workflow_with_non_and_decorated_task.py:my_flow",
)
Docs to help with deployment env and GitLab integration:
- Prefect GitLab integration: docs
- Customize job variables/env on deployments: docs
Can you share:
- Are you on gitlab.com or self-managed GitLab?Marvin
10/08/2025, 1:48 PMoleander-whirlwind
10/08/2025, 2:10 PMoleander-whirlwind
10/08/2025, 2:10 PMMarvin
10/08/2025, 2:10 PMMarvin
10/08/2025, 2:12 PMuvd/... to a full `https://gitlab.com/...`), or after switching from unauthenticated to token-authenticated URLs.
What to do now
1) Inspect the cached repo’s origin to see what Prefect cloned earlier
git -C /tmp/runner_storage/1e0894bf-93cf-4796-b2a4-158996457b05/uvd-workflow-orchestrator-example-prefect remote -v
You’ll likely see an origin like uvd/uvd-workflow-orchestrator-example-prefect (missing host) which doesn’t match your corrected <https://gitlab.com/>... URL.
2) Remove the cached storage directory, then retry
rm -rf /tmp/runner_storage/1e0894bf-93cf-4796-b2a4-158996457b05
Then re-run your deployment/serve. If you’re running in a container/worker, make sure that path isn’t persisted across runs.
3) Use a stable, fully qualified source going forward
- Recommended for private repos: use the GitLab integration so the remote URL stays stable and the token isn’t embedded in the URL.
import os
from prefect.flows import Flow
from prefect_gitlab.repositories import GitLabRepository
from prefect_gitlab.credentials import GitLabCredentials
repo = GitLabRepository(
repository="uvd/uvd-workflow-orchestrator-example-prefect", # full namespace, no .git
reference="main", # optional
host="<http://gitlab.com|gitlab.com>",
credentials=GitLabCredentials(token=os.environ["GITLAB_TOKEN"]),
)
flow = Flow.from_source(
source=repo,
entrypoint="workflows/workflow_with_non_and_decorated_task.py:my_flow",
)
- If you must embed the token in the URL (ensure no Slack formatting):
import os
from prefect.flows import Flow
git_url = f"<https://oauth2:{os.environ['GITLAB_TOKEN']}@gitlab.com/uvd/uvd-workflow-orchestrator-example-prefect.git>"
flow = Flow.from_source(
source=git_url,
entrypoint="workflows/workflow_with_non_and_decorated_task.py:my_flow",
)
Notes
- If the token value changes and you embed it in the origin URL, Prefect may see that as a “different repository.” Prefer the GitLabRepository(..., credentials=...) approach to keep the origin stable.
- Ensure the token is present in the environment where the clone happens (CI during build, or the worker at runtime).
- If you’re on a self-managed GitLab with a custom CA, make sure the container/host trusts that CA (the earlier SSL CA error points to that).
If you share how you’re running (local, Docker worker, K8s) and your current Flow.from_source(...) snippet, I can suggest the exact cleanup + configuration to make this stick.