Daniel
11/10/2025, 12:37 PMgoogle.auth.exceptions.DefaultCredentialsError: File /opt/prefect/atl-data-pipeline/amygdadss-8cde8d989be6.json was not found.
The json file is in the root where my prefect_dev.yaml file is, and my yaml looks like this:
name: atl-data-pipeline
prefect-version: 2.20.2
# build section allows you to manage and build docker images
build:
- prefect_docker.deployments.steps.build_docker_image:
id: build_image
requires: prefect-docker>=0.3.1
image_name: europe-west2-docker.pkg.dev/<org-name>/prefect-repo/atl-data-pipeline
tag: v1
dockerfile: ./Dockerfile
platform: linux/amd64
buildargs:
GITHUB_TOKEN: '{{ $GITHUB_TOKEN }}'
# push section allows you to manage if and how this project is uploaded to remote locations
push:
- prefect_docker.deployments.steps.push_docker_image:
requires: prefect-docker>=0.3.1
image_name: '{{ build_image.image_name }}'
tag: '{{ build_image.tag }}'
# pull section allows you to provide instructions for cloning this project in remote locations
pull:
- prefect.deployments.steps.set_working_directory:
directory: /opt/prefect/atl-data-pipeline
I am using prefect v2.20.2Marvin
11/10/2025, 12:37 PMMarvin
11/10/2025, 12:42 PM/opt/prefect/atl-data-pipeline/amygdadss-8cde8d989be6.json
Having the file next to your local prefect_dev.yaml doesn’t automatically make it available inside the container. You need to either bake it into the image, mount it at runtime, or avoid files entirely by using a Prefect GCP credentials block.
Here are three reliable approaches for Prefect 2.20.x:
1) Recommended: use a GcpCredentials block (no file mounts)
- Install the integration and register blocks once:
- pip install: pip install prefect-gcp
- register: prefect block register -m prefect_gcp
- Create the block (one-time), either in the UI or via code by pasting the JSON as `service_account_info`:
from prefect_gcp import GcpCredentials
import json
with open("amygdadss-8cde8d989be6.json") as f:
info = json.load(f)
GcpCredentials(service_account_info=info).save("my-gcp-creds", overwrite=True)
- Load and use in your flow:
from prefect import flow
from prefect_gcp import GcpCredentials
from google.cloud import storage
@flow
def run():
gcp = GcpCredentials.load("my-gcp-creds")
client = storage.Client(credentials=gcp.get_credentials_from_service_account())
print([b.name for b in client.list_buckets(project="my-project")])
if __name__ == "__main__":
run()
- Docs: GcpCredentials block, prefect-gcp overview
2) Mount the JSON into the container and set GOOGLE_APPLICATION_CREDENTIALS
- If you’re using a Docker work pool, add a volume and env var either on the work pool’s Base Job Template or per-deployment via job_variables. Example per-deployment:
deployments:
- name: atl-data-pipeline
work_pool:
name: my-docker-pool
job_variables:
image: europe-west2-docker.pkg.dev/<org-name>/prefect-repo/atl-data-pipeline:v1
env:
GOOGLE_APPLICATION_CREDENTIALS: /opt/prefect/atl-data-pipeline/sa.json
volumes:
- "/abs/path/on/worker-host/sa.json:/opt/prefect/atl-data-pipeline/sa.json:ro"
Important:
- The left side of the volume mapping must exist on the machine running the Docker worker (not your laptop).
- Set the env var to the container path.
- Docs: Docker worker, Work pools, Overriding job variables
3) Copy the JSON into the image (works, but be careful with secrets)
- In your Dockerfile:
COPY amygdadss-8cde8d989be6.json /opt/prefect/atl-data-pipeline/sa.json
ENV GOOGLE_APPLICATION_CREDENTIALS=/opt/prefect/atl-data-pipeline/sa.json
Then rebuild/push the image. Ensure your .dockerignore is not excluding the JSON if you intend to copy it. This approach embeds credentials in the image, so only use it if you’re comfortable with that risk and with your registry access controls.
A couple of clarifications that might help pinpoint your current issue:
- How are you setting GOOGLE_APPLICATION_CREDENTIALS right now? Is it in your Dockerfile, work pool base job template, or code?
- Which worker type are you using (Docker or Kubernetes)? If Kubernetes, you’ll want to mount the file via a Secret/volume and set the env var in the job manifest template.
- Can you share your Dockerfile and, if using a Docker work pool, the Base Job Template or job_variables you’re setting?
Also note: the pull: - prefect.deployments.steps.set_working_directory just sets the working directory inside the runtime; it does not copy secrets into the image or mount files.
Helpful docs:Marvin
11/10/2025, 12:42 PMDaniel
11/10/2025, 5:16 PM# Use the official Python image as a base image
FROM --platform=linux/amd64 python:3.10.12-slim
# Set the working directory in the container
WORKDIR /opt/prefect/atl-data-pipeline
# Install system dependencies, including Git and g++
RUN apt-get update && apt-get install -y \
gcc \
g++ \
libpq-dev \
git \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Set up build arguments to pass the GitHub token
ARG GITHUB_TOKEN
# Set environment variables
ENV GITHUB_TOKEN=$GITHUB_TOKEN
# ENV GOOGLE_APPLICATION_CREDENTIALS=/opt/prefect/atl-data-pipeline/amygdadss-8cde8d989be6.json
# Set Git buffer size to allow AmygdaAnalytics lib to be installed
RUN git config --global http.postBuffer 104857600
COPY requirements.txt .
# Modify requirements.txt to exclude shap
RUN sed -i '/shap/d' requirements.txt
# Install Python dependencies
RUN pip install --no-cache-dir -r requirements.txt
# Copy directories
COPY flows flows
COPY src src
# Copy individual files
COPY .env .
COPY .env.prod .
COPY .prefectignore .
COPY Dockerfile .
COPY prefect_dev.yaml .
COPY amygdadss-8cde8d989be6.json .
ENTRYPOINT ["python"]Marvin
11/10/2025, 5:16 PMMarvin
11/10/2025, 5:18 PM/opt/prefect/atl-data-pipeline/amygdadss-8cde8d989be6.json, so the path in the error should exist in the image if the correct image/tag is running.
- Your ENV GOOGLE_APPLICATION_CREDENTIALS=... line is commented out. Without it (or equivalent code that loads the JSON explicitly), Google auth won’t know to use that file. However, since the error specifically mentions that exact path, something is setting GOOGLE_APPLICATION_CREDENTIALS to that value at runtime (perhaps your work pool/job variables, cluster env, or code).
Given the above, there are two likely failure modes:
1) The running container does not have the file (tag mismatch, old image pulled by the worker, or different image referenced in the deployment/work pool).
2) GOOGLE_APPLICATION_CREDENTIALS is set in the container but points to a path where the file does not exist.
Quick checks to confirm:
- Verify the file is in the image you’re actually running:
docker pull europe-west2-docker.pkg.dev/<org-name>/prefect-repo/atl-data-pipeline:v1
docker run --rm -it europe-west2-docker.pkg.dev/<org-name>/prefect-repo/atl-data-pipeline:v1 ls -l /opt/prefect/atl-data-pipeline
You should see amygdadss-8cde8d989be6.json in the listing.
- Confirm the exact image/tag your deployment is using and that your worker is pulling that tag (workers may cache images).
Three reliable ways to fix it:
A) Keep the file in the image and set the env var in the image
- Un-comment the env line, rebuild, and push:
ENV GOOGLE_APPLICATION_CREDENTIALS=/opt/prefect/atl-data-pipeline/amygdadss-8cde8d989be6.json
Then rebuild/push and redeploy with the same tag, or bump the tag and update the deployment.
- To “fail fast” at build time if the file goes missing:
RUN test -f /opt/prefect/atl-data-pipeline/amygdadss-8cde8d989be6.json
B) Mount the JSON at runtime and set the env var via the work pool
- If you’re using a Docker work pool, define volumes and env in the Base Job Template or per-deployment `job_variables`:
job_variables:
image: europe-west2-docker.pkg.dev/<org-name>/prefect-repo/atl-data-pipeline:v1
env:
GOOGLE_APPLICATION_CREDENTIALS: /opt/prefect/atl-data-pipeline/sa.json
volumes:
- "/abs/path/on/worker-host/sa.json:/opt/prefect/atl-data-pipeline/sa.json:ro"
Notes:
- The left side must exist on the machine running the Docker worker (not your laptop).
- The env var must point to the container path.
- Docs: Docker worker, Work pools, Overriding job variables
C) Recommended long-term: use a Prefect GcpCredentials block (no file in image)
- Install/register once:
pip install prefect-gcp
prefect block register -m prefect_gcp
- Create the block with the JSON contents (one-time):
from prefect_gcp import GcpCredentials
import json
with open("amygdadss-8cde8d989be6.json") as f:
info = json.load(f)
GcpCredentials(service_account_info=info).save("my-gcp-creds", overwrite=True)
- Load and use in your flow (no GOOGLE_APPLICATION_CREDENTIALS needed):
from prefect import flow
from prefect_gcp import GcpCredentials
from google.cloud import storage
@flow
def run():
gcp = GcpCredentials.load("my-gcp-creds")
client = storage.Client(credentials=gcp.get_credentials_from_service_account())
print([b.name for b in client.list_buckets(project="my-project")])
if __name__ == "__main__":
run()Marvin
11/10/2025, 5:18 PMGOOGLE_APPLICATION_CREDENTIALS being set right now (Dockerfile, work pool job variables, or elsewhere)?
- What image reference (repo:tag) is your deployment using?
If you can share your work pool type and the deployment’s image and job variables, I’ll provide a concrete snippet aligned to your setup.Daniel
11/10/2025, 5:23 PMangel-class180-project % docker pull europe-west2-docker.pkg.dev/amygdadss/prefect-repo/atl-data-pipeline:v1
v1: Pulling from amygdadss/prefect-repo/atl-data-pipeline
Digest: sha256:a1e135bcf4d0a242e03cf55cfc80ebb079cad8ca16e3aa33f51569488c72f6fa
Status: Image is up to date for europe-west2-docker.pkg.dev/amygdadss/prefect-repo/atl-data-pipeline:v1
europe-west2-docker.pkg.dev/amygdadss/prefect-repo/atl-data-pipeline:v1
(atl_env) dansimons@Daniels-MacBook-Pro-5 angel-class180-project % docker run --rm -it europe-west2-docker.pkg.dev/amygdadss/prefect-repo/atl-data-pipeline:v1 ls -l /opt/prefect/atl-data-pipeline
WARNING: The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested
python: can't open file '/opt/prefect/atl-data-pipeline/ls': [Errno 2] No such file or directory
(atl_env) dansimons@Daniels-MacBook-Pro-5 angel-class180-project %Marvin
11/10/2025, 5:23 PMMarvin
11/10/2025, 5:24 PMENTRYPOINT ["python"], so when you run:
docker run ... IMAGE ls -l /opt/prefect/atl-data-pipeline
Docker treats ls -l /opt/prefect/atl-data-pipeline as arguments to the entrypoint, i.e. it tries to run:
python ls -l /opt/prefect/atl-data-pipeline
…which fails.
Do this to inspect the image correctly (also add --platform linux/amd64 on Apple Silicon):
docker run --rm -it --platform linux/amd64 --entrypoint /bin/sh europe-west2-docker.pkg.dev/amygdadss/prefect-repo/atl-data-pipeline:v1 -c "ls -l /opt/prefect/atl-data-pipeline; echo; echo GOOGLE_APPLICATION_CREDENTIALS=\$(printenv GOOGLE_APPLICATION_CREDENTIALS)"
A few concrete fixes and checks:
1) Verify the JSON is actually in the image/tag your worker is using
- If you don’t see the JSON after the command above, the pushed image likely doesn’t include it (e.g., built before you added COPY amygdadss-...json .).
- Rebuild and push with the correct platform and a new tag to avoid cache confusion:
docker buildx build --platform linux/amd64 -t europe-west2-docker.pkg.dev/amygdadss/prefect-repo/atl-data-pipeline:v2 .
docker push europe-west2-docker.pkg.dev/amygdadss/prefect-repo/atl-data-pipeline:v2
- Then re-check with:
docker run --rm -it --platform linux/amd64 --entrypoint /bin/sh europe-west2-docker.pkg.dev/amygdadss/prefect-repo/atl-data-pipeline:v2 -c "ls -l /opt/prefect/atl-data-pipeline"
2) Set GOOGLE_APPLICATION_CREDENTIALS in a reliable place
- Since you’re copying the JSON into the image, un-comment this in your Dockerfile so it’s set everywhere the image runs:
ENV GOOGLE_APPLICATION_CREDENTIALS=/opt/prefect/atl-data-pipeline/amygdadss-8cde8d989be6.json
Rebuild/push and update your deployment to use the new tag (or set pull policy to always pull).
- Alternatively, set the env var in your Docker work pool’s Base Job Template or per-deployment job_variables.env. Example per-deployment:
job_variables:
image: europe-west2-docker.pkg.dev/amygdadss/prefect-repo/atl-data-pipeline:v2
pull_policy: ALWAYS
env:
GOOGLE_APPLICATION_CREDENTIALS: /opt/prefect/atl-data-pipeline/amygdadss-8cde8d989be6.json
Docs: Docker worker, Work pools, Overriding job variables
3) Strongly consider removing the ENTRYPOINT
- With Prefect Docker workers, the worker sets the container command to run your flow. Having ENTRYPOINT ["python"] can interfere and make debugging harder (as you just experienced). It’s safer to remove that line so the worker’s command runs as intended.
4) Optional but recommended: use a GcpCredentials block instead of embedding the file
- This avoids shipping secrets in the image or mounting files:
- Install/register once:
pip install prefect-gcp
prefect block register -m prefect_gcp
- Create the block (one-time) with your JSON contents:
from prefect_gcp import GcpCredentials
import json
GcpCredentials(service_account_info=json.load(open("amygdadss-8cde8d989be6.json"))).save("my-gcp-creds", overwrite=True)
- Load in your flow:
from prefect_gcp import GcpCredentials
gcp = GcpCredentials.load("my-gcp-creds")
creds = gcp.get_credentials_from_service_account()
Docs: GcpCredentials block
If you share:
- the worker type (Docker or Kubernetes),
- the deployment’s image tag,
- and whether you prefer env var or Prefect block,
I can give you an exact snippet to match your setup.Daniel
11/10/2025, 5:29 PM(atl_env) dansimons@Daniels-MacBook-Pro-5 angel-class180-project % docker run --rm -it --platform linux/amd64 --entrypoint /bin/sh europe-west2-docker.pkg.dev/amygdadss/prefect-repo/atl-data-pipeline:v1 -c "ls -l /opt/prefect/atl-data-pipeline; echo; echo GOOGLE_APPLICATION_CREDENTIALS=\$(printenv GOOGLE_APPLICATION_CREDENTIALS)"
total 20
-rw-r--r-- 1 root root 1041 Sep 17 2024 Dockerfile
-rw-r--r-- 1 root root 2337 Sep 10 2024 amygdadss-8cde8d989be6.json
drwxr-xr-x 1 root root 44 Aug 19 12:06 flows
-rw-r--r-- 1 root root 7767 Aug 19 09:46 prefect_dev.yaml
-rw-r--r-- 1 root root 355 Jul 1 14:20 requirements.txt
drwxr-xr-x 1 root root 350 Aug 19 12:06 src
GOOGLE_APPLICATION_CREDENTIALS=Marvin
11/10/2025, 5:29 PMMarvin
11/10/2025, 5:31 PMENV GOOGLE_APPLICATION_CREDENTIALS=/opt/prefect/atl-data-pipeline/amygdadss-8cde8d989be6.json
- Rebuild and push with a new tag (on Apple Silicon, build for amd64):
docker buildx build --platform linux/amd64 -t europe-west2-docker.pkg.dev/amygdadss/prefect-repo/atl-data-pipeline:v2 .
docker push europe-west2-docker.pkg.dev/amygdadss/prefect-repo/atl-data-pipeline:v2
- Update your deployment to use :v2 and ensure your Docker work pool uses a pull policy that fetches the new tag:
job_variables:
image: europe-west2-docker.pkg.dev/amygdadss/prefect-repo/atl-data-pipeline:v2
pull_policy: ALWAYS
Option B — Set the env var via the Docker work pool (no Dockerfile change)
- Keep the file in the image and set the env var per-deployment (or in the Base Job Template):
job_variables:
image: europe-west2-docker.pkg.dev/amygdadss/prefect-repo/atl-data-pipeline:v1
pull_policy: ALWAYS
env:
GOOGLE_APPLICATION_CREDENTIALS: /opt/prefect/atl-data-pipeline/amygdadss-8cde8d989be6.json
- This tells the container to use the file you’ve already baked into the image.
Important checks
- Make sure the deployment’s image tag matches the image that actually contains the JSON (consider bumping to v2 to avoid cache confusion).
- Ensure the Docker worker is pulling the new image (pull_policy: ALWAYS), or restart the worker to clear any cached image.
- Consider removing the ENTRYPOINT ["python"] from the Dockerfile — Docker workers set the container command, and a custom entrypoint can interfere and makes debugging like this harder.
Alternative: Use a GcpCredentials block (recommended long-term)
- Avoids shipping the JSON in your image:
pip install prefect-gcp
prefect block register -m prefect_gcp
Create the block once:
from prefect_gcp import GcpCredentials
import json
GcpCredentials(service_account_info=json.load(open("amygdadss-8cde8d989be6.json"))).save("my-gcp-creds", overwrite=True)
Load in your flow:
from prefect_gcp import GcpCredentials
from google.cloud import storage
gcp = GcpCredentials.load("my-gcp-creds")
client = storage.Client(credentials=gcp.get_credentials_from_service_account())
Docs for reference:
- Docker worker: https://docs-2.prefect.io/latest/integrations/prefect-docker/worker/
- Work pools and job variables: https://docs-2.prefect.io/latest/concepts/work-pools/ and https://docs-2.prefect.io/latest/guides/deployment/overriding-job-variables/
- GCP credentials block: https://docs-2.prefect.io/latest/integrations/prefect-gcp/credentials/
If you can share:
- Your worker type (Docker or Kubernetes)
- How you’re currently setting env vars (Dockerfile vs work pool job_variables)
- The exact image tag referenced by the deployment
…I can give you a precise snippet tailored to your setup.Daniel
11/10/2025, 5:43 PMdocker buildx build --platform linux/amd64 -t europe-west2-docker.pkg.dev/amygdadss/prefect-repo/atl-data-pipeline:v2 .
docker push europe-west2-docker.pkg.dev/amygdadss/prefect-repo/atl-data-pipeline:v2
of this:
prefect deploy --name atl-full-day-flow_dev --prefect-file prefect_dev.yamlMarvin
11/10/2025, 5:43 PMMarvin
11/10/2025, 5:44 PMdocker buildx build --platform linux/amd64 -t europe-west2-docker.pkg.dev/amygdadss/prefect-repo/atl-data-pipeline:v1 .
docker push europe-west2-docker.pkg.dev/amygdadss/prefect-repo/atl-data-pipeline:v1
Ensure your Docker work pool pulls fresh images (pull_policy: ALWAYS) or restart the worker.
- If you’re changing the image tag or any deployment metadata, run prefect deploy (or update the deployment in the UI). Example:
docker buildx build --platform linux/amd64 -t europe-west2-docker.pkg.dev/amygdadss/prefect-repo/atl-data-pipeline:v2 .
docker push europe-west2-docker.pkg.dev/amygdadss/prefect-repo/atl-data-pipeline:v2
Then update the deployment to use :v2 — either by:
- Editing your prefect_dev.yaml and running:
prefect deploy --name atl-full-day-flow_dev --prefect-file prefect_dev.yaml
- Or editing the deployment’s job variables in the UI to set:
image: europe-west2-docker.pkg.dev/amygdadss/prefect-repo/atl-data-pipeline:v2
pull_policy: ALWAYS
Tip: Since your image already contains the JSON file, set the env var either in the Dockerfile:
ENV GOOGLE_APPLICATION_CREDENTIALS=/opt/prefect/atl-data-pipeline/amygdadss-8cde8d989be6.json
or in the deployment/job variables:
env:
GOOGLE_APPLICATION_CREDENTIALS: /opt/prefect/atl-data-pipeline/amygdadss-8cde8d989be6.json
Related docs:
- Docker worker: https://docs-2.prefect.io/latest/integrations/prefect-docker/worker/
- Work pools and job variables: https://docs-2.prefect.io/latest/concepts/work-pools/
- Overriding job variables in deployments: https://docs-2.prefect.io/latest/guides/deployment/overriding-job-variables/Daniel
11/10/2025, 5:45 PMMarvin
11/10/2025, 5:45 PMMarvin
11/10/2025, 5:50 PMprefect deployment inspect "YOUR_FLOW_NAME/atl-full-day-flow_dev"
- Look for:
- job_configuration.image
- job_configuration.env (does it set GOOGLE_APPLICATION_CREDENTIALS?)
- job_configuration.image_pull_policy
Docs: Overriding job variables
2) Inspect the work pool to see if the Base Job Template overrides image/env
- Run:
prefect work-pool inspect <your-docker-work-pool-name>
- Check the base job template for image, env, volumes that could differ from what you expect.
Docs: Work pools
3) Ensure the worker pulls your updated image
- In Prefect 2.x Docker workers, the job variable is image_pull_policy (not pull_policy). Valid values: Always, IfNotPresent, Never.
- Set it to Always to force fresh pulls:
# in your prefect_dev.yaml under this deployment
deployments:
- name: atl-full-day-flow_dev
work_pool:
name: <your-docker-pool>
job_variables:
image: europe-west2-docker.pkg.dev/amygdadss/prefect-repo/atl-data-pipeline:v2
image_pull_policy: Always
env:
GOOGLE_APPLICATION_CREDENTIALS: /opt/prefect/atl-data-pipeline/amygdadss-8cde8d989be6.json
Then re-run:
prefect deploy --name atl-full-day-flow_dev --prefect-file prefect_dev.yaml
Docs: Docker worker
4) Double-check the image actually has the file
You already validated locally with:
docker run --rm -it --platform linux/amd64 --entrypoint /bin/sh <image:tag> -c "ls -l /opt/prefect/atl-data-pipeline"
Do that on the exact tag you set in the deployment (v2, not v1), and make sure the file is listed. If you reused the same tag, the worker may still be using a cached image — that’s why step 3 is important.
5) Optional: set the env var in the image to simplify
If you prefer, set it in the Dockerfile:
ENV GOOGLE_APPLICATION_CREDENTIALS=/opt/prefect/atl-data-pipeline/amygdadss-8cde8d989be6.json
Rebuild/push (amd64) and update the deployment to use the new tag.
6) Optional: remove ENTRYPOINT
I recommend removing ENTRYPOINT ["python"] in your Dockerfile; Docker workers set the command for flow runs and a custom entrypoint can interfere and complicate debugging.
If you want a quick runtime check, add a tiny test to your flow:
from pathlib import Path
import os
from prefect import flow
@flow
def debug_env():
p = Path("/opt/prefect/atl-data-pipeline/amygdadss-8cde8d989be6.json")
print("File exists in container? ", p.exists())
print("GOOGLE_APPLICATION_CREDENTIALS=", os.getenv("GOOGLE_APPLICATION_CREDENTIALS"))
debug_env()
Run that via the deployment to confirm what the worker container actually sees.
If you share:
- Your Docker work pool name
- The output of prefect deployment inspect "FLOW/atl-full-day-flow_dev" (the image, env, image_pull_policy fields)
…I’ll pinpoint the exact mismatch and provide the precise change. Long-term, consider using a Prefect GcpCredentials block to avoid shipping JSON files: GcpCredentials block.Daniel
11/10/2025, 5:52 PMrefect block register -m prefect_gcp
Successfully registered 6 blocks
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Registered Blocks ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ BigQuery Warehouse │
│ GCP Cloud Run Job │
│ GCP Credentials │
│ GcpSecret │
│ GCS Bucket │
│ Vertex AI Custom Training Job │
└───────────────────────────────┘
Now where does the actual auth code of the gcp block live in my repo?
from prefect_gcp import GcpCredentials
import json
with open(“amygdadss-8cde8d989be6.json”) as f:
info = json.load(f)
GcpCredentials(service_account_info=info).save(“my-gcp-creds”, overwrite=True)Marvin
11/10/2025, 5:52 PMMarvin
11/10/2025, 5:53 PM# create_gcp_block.py
from prefect_gcp import GcpCredentials
import json
with open("amygdadss-8cde8d989be6.json") as f:
info = json.load(f)
GcpCredentials(service_account_info=info).save("my-gcp-creds", overwrite=True)
print("Saved GCP block: my-gcp-creds")
- Then run:
python create_gcp_block.py
- After this, you can delete the JSON file locally if you want. It does not need to be in your image or deployment.
- Use the block in your flow (no GOOGLE_APPLICATION_CREDENTIALS needed):
from prefect import flow
from prefect_gcp import GcpCredentials
from google.cloud import storage
@flow
def run():
gcp = GcpCredentials.load("my-gcp-creds")
creds = gcp.get_credentials_from_service_account()
client = storage.Client(credentials=creds)
for b in client.list_buckets(project="YOUR_GCP_PROJECT"):
print(b.name)
if __name__ == "__main__":
run()
- Make sure your runtime image has the right dependencies:
- Add to requirements.txt (or pip install in Dockerfile):
- prefect-gcp
- google-cloud-storage (and any other google-cloud-* libs you use)
- Rebuild/push your image as needed.
- Ensure your worker/flow can reach your Prefect workspace to load the block:
- For Prefect Cloud, your Docker worker usually sets the required env automatically.
- If needed, set PREFECT_API_URL and PREFECT_API_KEY in your worker environment.
- You can also create the block via the UI (no code needed):
- In Prefect UI: Blocks > + Create block > GCP Credentials > paste the JSON key as “Service Account Info” and save as “my-gcp-creds”.
- Docs: GcpCredentials block
What to remove now
- Remove any GOOGLE_APPLICATION_CREDENTIALS references from your Dockerfile/deployment. You don’t need that env var when using the block.
- You no longer need the JSON file inside the image.
If you want, share your requirements.txt and I’ll double-check the minimal packages you’ll need for your specific GCP services.Daniel
11/10/2025, 6:00 PMimport pandas as pd
import numpy as np
import warnings
import os, sys
import matplotlib.pyplot as plt
from google.cloud import storage
import re
from sqlalchemy import text
from src.common.log import logger
from src.config import Config
from pandas.api.types import is_integer_dtype
warnings.filterwarnings("ignore")
from IPython.core.ultratb import ColorTB
sys.excepthook = ColorTB()
client = storage.Client()
def rawdata_load_and_transform(file_name, bucket_name):
<http://logger.info|logger.info>(f"Processing rawdata file: {file_name} from bucket: {bucket_name}")
# Load Excel file from GCS
bucket = client.get_bucket(bucket_name)
blob = bucket.blob(file_name)
content = blob.download_as_bytes()
all_sheets = pd.read_excel(content, sheet_name=None)
<http://logger.info|logger.info>(f"File {file_name} loaded from bucket {bucket_name}")Marvin
11/10/2025, 6:00 PMMarvin
11/10/2025, 6:00 PMstorage.Client() at import time and instead inject the client (or credentials) from your flow. Here are two solid patterns:
Pattern A (recommended): dependency injection from the flow
- Create the client in the flow using the Prefect block, then pass it into your helper functions.
Flow:
from prefect import flow
from prefect_gcp import GcpCredentials
from google.cloud import storage
from src.raw_module import rawdata_load_and_transform # adjust import
@flow
def main(file_name: str, bucket_name: str):
gcp = GcpCredentials.load("my-gcp-creds")
creds = gcp.get_credentials_from_service_account()
# Optional: pass project explicitly if needed
storage_client = storage.Client(credentials=creds, project="YOUR_GCP_PROJECT")
rawdata_load_and_transform(file_name, bucket_name, storage_client=storage_client)
if __name__ == "__main__":
main("path/in/bucket.xlsx", "your-bucket")
Your module (remove the global client and accept it as a parameter):
import pandas as pd
from google.cloud import storage
from io import BytesIO
from src.common.log import logger
def rawdata_load_and_transform(file_name, bucket_name, storage_client: storage.Client):
<http://logger.info|logger.info>(f"Processing rawdata file: {file_name} from bucket: {bucket_name}")
bucket = storage_client.bucket(bucket_name) # or storage_client.get_bucket(...)
blob = bucket.blob(file_name)
content = blob.download_as_bytes()
all_sheets = pd.read_excel(BytesIO(content), sheet_name=None)
<http://logger.info|logger.info>(f"File {file_name} loaded from bucket {bucket_name}")
return all_sheets
Benefits:
- Your non-Prefect code stays decoupled from Prefect.
- No reliance on GOOGLE_APPLICATION_CREDENTIALS or files in the image.
- Easy to test locally by passing a default client.
Pattern B: lazy creation inside the helper (introduces a Prefect dependency)
- If you can tolerate a small Prefect dependency in that module, you can load the block at call time. Avoid doing it at import time.
from functools import lru_cache
from google.cloud import storage
@lru_cache(maxsize=1)
def _get_storage_client():
try:
from prefect_gcp import GcpCredentials
gcp = GcpCredentials.load("my-gcp-creds")
creds = gcp.get_credentials_from_service_account()
return storage.Client(credentials=creds, project="YOUR_GCP_PROJECT")
except Exception:
# Fallback to ADC (e.g., local dev with gcloud auth application-default login)
return storage.Client()
def rawdata_load_and_transform(file_name, bucket_name):
client = _get_storage_client()
bucket = client.bucket(bucket_name)
blob = bucket.blob(file_name)
content = blob.download_as_bytes()
...
Important: remove any top-level client = storage.Client() in your modules. Initializing the client at import time will bypass the block and rely on ADC, which is what caused the “JSON not found” error previously.
Don’t forget runtime deps
- Ensure your image includes:
- prefect-gcp
- google-cloud-storage (and any other google-cloud-* libs you use)
- Rebuild/push your image after updating requirements.
Docs for reference:
- GcpCredentials block
If you share your flow entrypoint and where this helper lives, I can suggest the exact edits to wire it up end-to-end.