---
title: "GCP Deployment"
description: "Deploy Bifrost Enterprise on GCP using Artifact Registry with Workload Identity"
icon: "google"
---

Bifrost Enterprise images for GCP customers are distributed through GCP Artifact Registry, enabling native Workload Identity for secure, keyless authentication.

## Architecture

```mermaid
flowchart LR
    subgraph GCP[GCP Project]
        subgraph GKE[GKE Cluster]
            Pod[Bifrost Pod]
            KSA[K8s ServiceAccount]
        end
        GSA[GCP Service Account]
        AR[Artifact Registry<br/>Bifrost Images]
    end
    
    KSA -->|Workload Identity| GSA
    Pod -->|Impersonates| GSA
    GSA -->|Pull Permission| AR
    AR -->|Image| Pod
```

## Prerequisites

- GKE cluster (v1.24+) with Workload Identity enabled
- `gcloud` CLI configured with appropriate permissions
- `kubectl` configured for your GKE cluster
- Your GCP project allowlisted by Bifrost team

<Note>
Contact the Bifrost team with your GCP project ID and service account email to get access configured.
</Note>

## Workload Identity (Recommended)

Workload Identity provides the most secure authentication method for GKE deployments by eliminating the need for service account keys.

### Step 1: Enable Workload Identity on GKE

If not already enabled, enable Workload Identity on your cluster:

```bash
# For existing cluster
gcloud container clusters update YOUR_CLUSTER_NAME \
  --region=YOUR_REGION \
  --workload-pool=YOUR_PROJECT_ID.svc.id.goog

# Verify Workload Identity is enabled
gcloud container clusters describe YOUR_CLUSTER_NAME \
  --region=YOUR_REGION \
  --format="value(workloadIdentityConfig.workloadPool)"
```

### Step 2: Create GCP Service Account

Create a service account that will be used to pull images:

```bash
# Create service account
gcloud iam service-accounts create bifrost-pull-sa \
  --display-name="Bifrost Image Pull SA" \
  --project=YOUR_PROJECT_ID
```

### Step 3: Request Access from Bifrost Team

Provide the following to the Bifrost team:
- Your GCP project ID
- Service account email: `bifrost-pull-sa@YOUR_PROJECT_ID.iam.gserviceaccount.com`

The Bifrost team will grant the necessary permissions to pull images from the registry.

### Step 4: Create Namespace and ServiceAccount

```bash
kubectl create namespace bifrost
```

```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: bifrost-sa
  namespace: bifrost
  annotations:
    iam.gke.io/gcp-service-account: bifrost-pull-sa@YOUR_PROJECT_ID.iam.gserviceaccount.com
```

### Step 5: Bind Kubernetes SA to GCP SA

Allow the Kubernetes ServiceAccount to impersonate the GCP Service Account:

```bash
gcloud iam service-accounts add-iam-policy-binding \
  bifrost-pull-sa@YOUR_PROJECT_ID.iam.gserviceaccount.com \
  --role=roles/iam.workloadIdentityUser \
  --member="serviceAccount:YOUR_PROJECT_ID.svc.id.goog[bifrost/bifrost-sa]"
```

### Step 6: Create Image Pull Secret with Token Refresh

Artifact Registry tokens expire after 60 minutes. Use a CronJob to refresh the imagePullSecret:

```yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: refresh-ar-secret
  namespace: bifrost
spec:
  schedule: "*/30 * * * *"  # Every 30 minutes
  successfulJobsHistoryLimit: 1
  failedJobsHistoryLimit: 3
  jobTemplate:
    spec:
      template:
        spec:
          serviceAccountName: bifrost-sa
          containers:
          - name: token-refresh
            image: google/cloud-sdk:slim
            command: ["/bin/bash", "-c"]
            args:
            - |
              set -e
              
              # Get access token using Workload Identity
              TOKEN=$(gcloud auth print-access-token)
              
              # Delete existing secret if it exists
              kubectl delete secret ar-pull-secret --ignore-not-found -n bifrost
              
              # Create new imagePullSecret
              kubectl create secret docker-registry ar-pull-secret \
                --docker-server=REGION-docker.pkg.dev \
                --docker-username=oauth2accesstoken \
                --docker-password="$TOKEN" \
                -n bifrost
              
              echo "Secret refreshed at $(date)"
          restartPolicy: OnFailure
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: secret-manager
  namespace: bifrost
rules:
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get", "create", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: secret-manager-binding
  namespace: bifrost
subjects:
- kind: ServiceAccount
  name: bifrost-sa
  namespace: bifrost
roleRef:
  kind: Role
  name: secret-manager
  apiGroup: rbac.authorization.k8s.io
```

<Warning>
Replace `REGION` with your Artifact Registry region (e.g., `us-central1`).
</Warning>

### Step 7: Deploy Bifrost

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bifrost
  namespace: bifrost
spec:
  replicas: 2
  selector:
    matchLabels:
      app: bifrost
  template:
    metadata:
      labels:
        app: bifrost
    spec:
      serviceAccountName: bifrost-sa
      imagePullSecrets:
      - name: ar-pull-secret
      containers:
      - name: bifrost
        image: REGION-docker.pkg.dev/BIFROST_PROJECT/YOUR_HUB_SLUG/bifrost:latest
        ports:
        - containerPort: 8080
          name: http
        resources:
          requests:
            cpu: "250m"
            memory: "512Mi"
          limits:
            cpu: "1000m"
            memory: "2Gi"
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 10
          periodSeconds: 5
        volumeMounts:
        - name: config
          mountPath: /app/data/config.json
          subPath: config.json
      volumes:
      - name: config
        secret:
          secretName: bifrost-config
---
apiVersion: v1
kind: Service
metadata:
  name: bifrost
  namespace: bifrost
spec:
  selector:
    app: bifrost
  ports:
  - port: 80
    targetPort: 8080
    protocol: TCP
  type: ClusterIP
```

### Bootstrap: Initial Secret Creation

Before the first deployment, manually create the initial imagePullSecret:

```bash
# Authenticate gcloud
gcloud auth login

# Create initial secret
kubectl create secret docker-registry ar-pull-secret \
  --docker-server=REGION-docker.pkg.dev \
  --docker-username=oauth2accesstoken \
  --docker-password="$(gcloud auth print-access-token)" \
  -n bifrost
```

## Service Account Impersonation

For cross-project deployments or when you need to use an existing service account:

### Configure Impersonation

```bash
# Grant impersonation permission
gcloud iam service-accounts add-iam-policy-binding \
  BIFROST_PROVIDED_SA@BIFROST_PROJECT.iam.gserviceaccount.com \
  --role=roles/iam.serviceAccountTokenCreator \
  --member="serviceAccount:bifrost-pull-sa@YOUR_PROJECT_ID.iam.gserviceaccount.com"
```

### Token Refresh with Impersonation

Update the CronJob to use impersonation:

```yaml
args:
- |
  set -e
  
  # Get access token by impersonating the Bifrost SA
  TOKEN=$(gcloud auth print-access-token \
    --impersonate-service-account=BIFROST_PROVIDED_SA@BIFROST_PROJECT.iam.gserviceaccount.com)
  
  kubectl delete secret ar-pull-secret --ignore-not-found -n bifrost
  kubectl create secret docker-registry ar-pull-secret \
    --docker-server=REGION-docker.pkg.dev \
    --docker-username=oauth2accesstoken \
    --docker-password="$TOKEN" \
    -n bifrost
```

## Service Account Key (Legacy)

<Warning>
Service account keys are not recommended for production. Use Workload Identity instead.
</Warning>

For environments that cannot use Workload Identity:

```bash
# Create key (provided by Bifrost team)
# Store key securely

# Create imagePullSecret
kubectl create secret docker-registry ar-pull-secret \
  --docker-server=REGION-docker.pkg.dev \
  --docker-username=_json_key \
  --docker-password="$(cat sa-key.json)" \
  -n bifrost
```

## Verifying Access

### Test Artifact Registry Authentication

```bash
# Configure docker for Artifact Registry
gcloud auth configure-docker REGION-docker.pkg.dev

# Pull test (requires impersonation or direct access)
docker pull REGION-docker.pkg.dev/BIFROST_PROJECT/YOUR_HUB_SLUG/bifrost:latest
```

### Verify Workload Identity Configuration

```bash
# Check ServiceAccount annotation
kubectl get sa bifrost-sa -n bifrost -o yaml

# Verify pod can authenticate
kubectl exec -it deployment/bifrost -n bifrost -- \
  gcloud auth print-access-token

# Check token refresh CronJob
kubectl get cronjob refresh-ar-secret -n bifrost
kubectl get jobs -n bifrost
```

## Troubleshooting

### ImagePullBackOff Errors

1. **Check imagePullSecret exists**: `kubectl get secret ar-pull-secret -n bifrost`
2. **Verify token is valid**: Check if CronJob ran successfully
3. **Check Workload Identity binding**: Ensure GCP SA is bound to K8s SA

```bash
# Check pod events
kubectl describe pod -l app=bifrost -n bifrost

# Manually refresh token
kubectl create job --from=cronjob/refresh-ar-secret manual-refresh -n bifrost
```

### Workload Identity Issues

```bash
# Verify Workload Identity pool
gcloud container clusters describe YOUR_CLUSTER_NAME \
  --region=YOUR_REGION \
  --format="value(workloadIdentityConfig.workloadPool)"

# Check IAM binding
gcloud iam service-accounts get-iam-policy \
  bifrost-pull-sa@YOUR_PROJECT_ID.iam.gserviceaccount.com
```

### Token Expiration

If pods fail to pull images after 60 minutes:

1. Verify CronJob is running: `kubectl get cronjob -n bifrost`
2. Check CronJob logs: `kubectl logs -l job-name=refresh-ar-secret -n bifrost`
3. Manually trigger refresh: `kubectl create job --from=cronjob/refresh-ar-secret manual-refresh -n bifrost`

## Next Steps

- Configure [Bifrost settings](/quickstart/gateway/setting-up) for your use case
- Set up [observability](/features/observability/default) for monitoring
- Enable [clustering](/enterprise/clustering) for high availability
