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
gcloudCLI 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:
- Configure the OAuth consent screen with your domain and support email
- Create an OAuth 2.0 Client ID of type Web application in the Credentials console
- 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
| Stack | Description | GCP service used |
|---|---|---|
| Observability | Prometheus, Grafana, Loki, Tempo | GCS buckets for Thanos, Loki, Tempo long-term storage |
| Secret management | External Secrets Operator | Secret Manager |
| DNS management | External DNS | Cloud DNS |
| TLS certificates | cert-manager | Cloud DNS for DNS-01 challenges |
| Backup | Velero | GCS bucket for backup storage |
| Encryption keys | SOPS / KMS provider | Cloud KMS |
| Policy enforcement | Kyverno | — |
| Access proxy | OAuth2 Proxy | Cloud 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