Portefaix docs GitHub

Deploy Portefaix on GCP

This guide shows you how to deploy a production-ready Portefaix platform on Google Cloud Platform using a GCP Organization with dedicated project hierarchy, GKE with Workload Identity Federation for keyless authentication, and GitHub Actions for infrastructure automation.

Goal: a running GKE cluster in a dedicated GCP project, with Portefaix stacks continuously reconciled by ArgoCD and GCP service accounts bound to Kubernetes workloads via Workload Identity — no service account keys stored anywhere in the cluster.

Prerequisites

  • A GCP Organization created via Google Workspace or Cloud Identity
  • gcloud CLI v450+ installed and authenticated (gcloud auth login)
  • Terraform ≥ 1.5 and kubectl installed locally
  • Organization Administrator or Owner role on the GCP Organization
  • A GitHub organisation for your GitOps repositories

1. Authenticate and identify your organization

Log in and confirm your organization ID — you will need it for all subsequent steps:

gcloud auth login
gcloud auth application-default login

# Find your organization ID
gcloud organizations list

Set your environment variables for the session:

export GCP_ORG_ID="123456789012"          # from organizations list
export GCP_ORG_NAME="portefaix"            # short slug for resource names
export GCP_USER="admin@portefaix.xyz"      # your GCP identity
export GCP_BILLING_ACCOUNT="XXXXXX-XXXXXX-XXXXXX"
export GCP_REGION="europe-west1"
export PORTEFAIX_ENV="staging"

2. Bootstrap the GCP organization

Portefaix uses a dedicated bootstrap project to hold the Terraform service account and state bucket. This project is separate from workload projects and gives you a clean separation of concerns for infrastructure management.

Create the bootstrap project

# Create the bootstrap project under your organization
gcloud projects create $GCP_ORG_NAME-bootstrap \
  --organization=$GCP_ORG_ID \
  --name="Portefaix Bootstrap"

# Link billing
gcloud billing projects link $GCP_ORG_NAME-bootstrap \
  --billing-account=$GCP_BILLING_ACCOUNT

export GCP_BOOTSTRAP_PROJECT="$GCP_ORG_NAME-bootstrap"

Enable required APIs on the bootstrap project

gcloud services enable \
  cloudresourcemanager.googleapis.com \
  cloudbilling.googleapis.com \
  iam.googleapis.com \
  serviceusage.googleapis.com \
  storage.googleapis.com \
  --project=$GCP_BOOTSTRAP_PROJECT

Create the Terraform service account

gcloud iam service-accounts create terraform \
  --display-name="Portefaix Terraform" \
  --project=$GCP_BOOTSTRAP_PROJECT

export TF_SA="terraform@$GCP_BOOTSTRAP_PROJECT.iam.gserviceaccount.com"

# Grant organization-level permissions needed to create projects and IAM bindings
gcloud organizations add-iam-policy-binding $GCP_ORG_ID \
  --member="serviceAccount:$TF_SA" \
  --role="roles/resourcemanager.organizationAdmin"

gcloud organizations add-iam-policy-binding $GCP_ORG_ID \
  --member="serviceAccount:$TF_SA" \
  --role="roles/billing.user"

Create the GCS bucket for Terraform state

gcloud storage buckets create gs://$GCP_ORG_NAME-tfstate \
  --project=$GCP_BOOTSTRAP_PROJECT \
  --location=$GCP_REGION \
  --uniform-bucket-level-access \
  --public-access-prevention

# Enable versioning for state history
gcloud storage buckets update gs://$GCP_ORG_NAME-tfstate \
  --versioning

3. Bootstrap the organization with Terraform

The root Terraform module creates the project hierarchy (folder structure, workload projects, shared networking) and provisions the Workload Identity Pool used by GitHub Actions:

cd portefaix-infrastructure/terraform/gcp/root
cp terraform.tfvars.example terraform.tfvars

Edit terraform.tfvars:

org_id           = "123456789012"
org_name         = "portefaix"
billing_account  = "XXXXXX-XXXXXX-XXXXXX"
region           = "europe-west1"
tf_sa_email      = "terraform@portefaix-bootstrap.iam.gserviceaccount.com"
terraform init \
  -backend-config="bucket=$GCP_ORG_NAME-tfstate" \
  -backend-config="prefix=root"

terraform plan -out=tfplan
terraform apply tfplan

Groups and billing: after running the root module, visit the GCP Organization Setup console to create the required IAM groups and verify the billing account assignment.

4. Provision the GKE cluster

Each environment (staging, production) has its own GCP project. The GKE Terraform module creates the cluster, VPC, node pools, and all Workload Identity bindings:

cd portefaix-infrastructure/terraform/gcp/gke
cp terraform.tfvars.example terraform.tfvars

Key variables in terraform.tfvars:

project_id     = "portefaix-staging"
region         = "europe-west1"
cluster_name   = "portefaix-staging"
# Use Autopilot for a fully managed node pool experience
autopilot      = true
terraform init \
  -backend-config="bucket=$GCP_ORG_NAME-tfstate" \
  -backend-config="prefix=gke/$PORTEFAIX_ENV"

terraform plan -out=tfplan
terraform apply tfplan

5. Configure Workload Identity

Workload Identity Federation maps Kubernetes service accounts to GCP service accounts, allowing pods to call Google Cloud APIs using short-lived tokens — no service account key files required. The Terraform module creates all bindings automatically. Verify them:

# Check the Workload Identity Pool on the project
gcloud iam workload-identity-pools list \
  --location=global \
  --project=portefaix-staging

# Verify the annotation on a Kubernetes service account (after cluster access below)
kubectl get serviceaccount external-secrets -n external-secrets \
  -o jsonpath='.metadata.annotations'

Expected annotation on each platform service account:

iam.gke.io/gcp-service-account: external-secrets@portefaix-staging.iam.gserviceaccount.com

6. Fetch cluster credentials

Load Portefaix credentials and update your local kubeconfig:

. ./portefaix.sh gcp

gcloud container clusters get-credentials \
  portefaix-$PORTEFAIX_ENV \
  --region $GCP_REGION \
  --project portefaix-$PORTEFAIX_ENV

Verify nodes are ready:

kubectl get nodes
NAME                                                STATUS   ROLES    AGE     VERSION
gke-portefaix-staging-core-5d5d62be-tf15            Ready    <none>   7m      v1.31.1-gke.1000000

7. Configure Cloud IAP (optional)

Cloud Identity-Aware Proxy (IAP) secures access to the ArgoCD UI and Grafana without exposing them publicly. To enable IAP:

  1. Configure the OAuth consent screen with your domain and support email
  2. Create an OAuth 2.0 Client ID of type Web application in the Credentials console
  3. Store the client ID and secret in Secret Manager — the External Secrets Operator will sync them to your cluster automatically
gcloud secrets create iap-oauth-client-id \
  --data-file=- \
  --project=portefaix-$PORTEFAIX_ENV <<< "YOUR_CLIENT_ID"

gcloud secrets create iap-oauth-client-secret \
  --data-file=- \
  --project=portefaix-$PORTEFAIX_ENV <<< "YOUR_CLIENT_SECRET"

8. Deploy Portefaix stacks via ArgoCD

Install ArgoCD with the GCP-specific values file, then apply the app-of-apps bootstrap:

helm repo add argo https://argoproj.github.io/argo-helm
helm repo update

helm install argocd argo/argo-cd \
  --namespace argocd --create-namespace \
  --values portefaix-kubernetes/gitops/argocd/values-gcp.yaml \
  --wait

kubectl apply -f portefaix-kubernetes/gitops/argocd/bootstrap/app-of-apps-gcp-$PORTEFAIX_ENV.yaml

Monitor the initial sync:

argocd app list
argocd app wait portefaix-bootstrap --health --timeout 600

9. Automate with GitHub Actions and Workload Identity Federation

The Portefaix infrastructure repository uses GitHub Actions with Workload Identity Federation for all Terraform operations — no GCP service account keys stored as GitHub secrets.

The root Terraform module creates the Workload Identity Pool and Provider for GitHub Actions. Verify the configuration:

# List Workload Identity Pools
gcloud iam workload-identity-pools list \
  --location=global \
  --project=$GCP_BOOTSTRAP_PROJECT

# List providers in the pool
gcloud iam workload-identity-pools providers list \
  --workload-identity-pool=github-actions \
  --location=global \
  --project=$GCP_BOOTSTRAP_PROJECT

Once configured, GitHub Actions workflows authenticate with:

# In .github/workflows/terraform-gcp.yml
- uses: google-github-actions/auth@v2
  with:
    workload_identity_provider: "projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/github-actions/providers/github"
    service_account: "terraform@$GCP_BOOTSTRAP_PROJECT.iam.gserviceaccount.com"

No key rotation needed: Workload Identity Federation tokens are short-lived (1 hour) and tied to the specific repository and branch ref. A compromised token cannot be reused outside its scope.

Stacks available on GCP

StackDescriptionGCP service used
ObservabilityPrometheus, Grafana, Loki, TempoGCS buckets for Thanos, Loki, Tempo long-term storage
Secret managementExternal Secrets OperatorSecret Manager
DNS managementExternal DNSCloud DNS
TLS certificatescert-managerCloud DNS for DNS-01 challenges
BackupVeleroGCS bucket for backup storage
Encryption keysSOPS / KMS providerCloud KMS
Policy enforcementKyverno
Access proxyOAuth2 ProxyCloud IAP

Troubleshooting

Workload Identity not working

Verify the cluster was created with the Workload Identity feature enabled:

gcloud container clusters describe portefaix-$PORTEFAIX_ENV \
  --region $GCP_REGION \
  --project portefaix-$PORTEFAIX_ENV \
  --format="value(workloadIdentityConfig.workloadPool)"

Expected output: portefaix-staging.svc.id.goog

Verify the IAM binding between the GCP service account and the Kubernetes service account:

gcloud iam service-accounts get-iam-policy \
  external-secrets@portefaix-staging.iam.gserviceaccount.com \
  --project=portefaix-staging

The policy should include a binding for serviceAccount:portefaix-staging.svc.id.goog[external-secrets/external-secrets] with role roles/iam.workloadIdentityUser.

gcloud credential errors after sourcing portefaix.sh

# Re-authenticate application default credentials
gcloud auth application-default login

# Verify the active account matches your GCP_USER
gcloud auth list