Google Cloud Platform (GCP) Provider
Setup
Authentication uses a Google Cloud Service Account JSON key file.
- Go to the Google Cloud Console → IAM & Admin → Service Accounts.
- Click Create Service Account, assign it the relevant roles, and click Done.
- Click on the created service account, go to the Keys tab, click Add Key → Create new key (JSON format).
- Save the downloaded JSON file securely.
Authentication Config
Puls dynamically resolves the GCP configuration through several fallbacks to make local and CI usage painless:
- Explicit GCP configuration:
- Firebase reuse fallback: If you are already using the Firebase provider, GCP automatically reuses
Config.providers.firebaseconfiguration. - Environment variables: If config objects are omitted, Puls checks the
GCP_SAorFIREBASE_SAenvironment variables pointing to a JSON key file. SetGCP_SSH_USERto configure the default SSH user for VM and Template provisioning.
Cloud Run
Deploy containerized microservices to Google Cloud Run v2 with support for custom CPU, memory limits, autoscaling, custom regions, environment variables, and public IAM invoker policies.
GCP.CloudRun("my-web-service")
.image("gcr.io/my-project/my-image:latest")
.port(8080)
.cpu("1")
.memory("512Mi")
.minInstances(0)
.maxInstances(10)
.env({
NODE_ENV: "production",
DATABASE_URL: "postgresql://...",
})
.region("us-central1")
.public(true)
Cloud Run API Reference
| Method | Type | Description | Default |
|---|---|---|---|
.image(img) |
string |
Required. The container image URL (e.g. Artifact Registry or GCR). | - |
.port(p) |
number |
The container port to expose. | 8080 |
.cpu(c) |
string \| number |
CPU limits (e.g., "1", 2, "1000m"). Numbers are stringified. |
"1" |
.memory(m) |
string \| number |
Memory limits (e.g., "512Mi", 1024, "2Gi"). Numbers are treated as Mi. |
"512Mi" |
.minInstances(n) |
number |
Minimum instance scaling. | 0 |
.maxInstances(n) |
number |
Maximum instance scaling. | 100 |
.env(vars) |
Record<string, string \| GCPSecretBuilder> |
Key-value pairs of environment variables to inject. Values can be either raw strings or GCP.Secret builders. |
{} |
.region(name) |
string |
Custom GCP region. | "us-central1" |
.public(enabled) |
boolean |
Toggles whether the service is publicly accessible via the internet. | true |
Cloud SQL
Provision and manage PostgreSQL and MySQL relational database instances on Google Cloud SQL, with support for automatic engine mappings, custom sizing, custom database initialization, secure networking, and credentials management.
GCP.CloudSQL("my-database")
.engine({ engine: "postgres", version: "16" })
.size("db-f1-micro")
.storage(20)
.credentials("db_user", "secure_password")
.database("my_app_db")
.publicAccess(false)
Cloud SQL API Reference
| Method | Type | Description | Default |
|---|---|---|---|
.engine(e) |
{ engine: "postgres" \| "mysql", version: string } |
Database engine and version. Version is mapped automatically (e.g. postgres, 16 -> POSTGRES_16). |
postgres, 16 |
.size(tier) |
string |
Machine tier (e.g. "db-f1-micro", "db-g1-small", "db-custom-2-7680"). |
"db-f1-micro" |
.storage(gb) |
number |
Storage size in GB. Disk size can only be increased, not decreased. | 10 |
.credentials(user, pwd) |
string, string |
Required. The master username and password. If a custom user is specified (not postgres or root), it is automatically created post-deploy. |
- |
.database(name) |
string |
Custom database name to be created inside the instance post-deploy. | - |
.publicAccess(enabled) |
boolean |
Toggles whether 0.0.0.0/0 is authorized to access the public IP of the database. |
false |
.region(name) |
string |
Custom GCP region. | "us-central1" |
Outputs
endpoint(PRIMARYIP address, once available).port(mapped according to the selected engine, e.g.,5432for Postgres,3306for MySQL).connectionName(the connection stringproject:region:instancerequired to connect via Cloud SQL Auth Proxy).
Secret Manager
Manage sensitive settings and keys on Google Cloud Secret Manager. Values are automatically base64-encoded on upload and safely decoded upon retrieval. You can wire GCP.Secret instances directly to GCP.CloudRun environment variables for seamless, secure value injection.
Secret Manager API Reference
| Method | Type | Description | Default |
|---|---|---|---|
.plainText(value) |
string |
Sets the raw plaintext value of the secret. Automatically pushes a new version when deployed if it differs from the active value. | - |
.keyValue(obj) |
object |
Serializes an object to JSON string format before storing it. | - |
Pub/Sub (Topics & Subscriptions)
Create and manage asynchronous event-driven queues on GCP Pub/Sub. Puls separates decoupled messaging into two first-class builders: GCP.PubSub.Topic (analogous to AWS SNS) and GCP.PubSub.Subscription (analogous to AWS SQS).
// 1. Topic definition
const myTopic = GCP.PubSub.Topic("user-events");
// 2. Pull-based Subscription
GCP.PubSub.Subscription("user-events-pull")
.topic(myTopic)
.ackDeadline(20);
// 3. Push-based Subscription
GCP.PubSub.Subscription("user-events-push")
.topic(myTopic)
.pushEndpoint("https://my-api-service.run.app/events")
.ackDeadline(10);
Topic API Reference
No additional configurations are required to deploy a basic Pub/Sub topic container.
resolvedTopicName(resolves to the full Google Cloud topic resource pathprojects/{project}/topics/{topicId}).
Subscription API Reference
| Method | Type | Description | Default |
|---|---|---|---|
.topic(t) |
string \| GCPPubSubTopicBuilder |
Required. The topic ID or topic builder instance to bind this subscription to. | - |
.pushEndpoint(url) |
string |
Optional. Setting this configures a Push Subscription that automatically posts event payloads to the given URL. Omit this to create a default Pull Subscription. | - |
.ackDeadline(seconds) |
number |
Acknowledgment deadline in seconds. | 10 |
Cloud DNS
Provision and manage Google Cloud DNS Hosted Zones and record sets with 100% parity to AWS Route53 and DigitalOcean Domain. Features auto-trailing-dot formatting, uppercase alphanumeric zone ID conversions, transactional record updates (deleting the old set and adding the new one in a single request), dynamic cross-resource target resolving, and hybrid configuration loading from local files.
GCP.CloudDNS("mycompany.com")
.record("config/records.yaml") // Bulk load static records from a YAML or JSON file!
.record("api", "A", "10.0.0.9", 120) // Programmatic hybrid chaining!
.pointer("www", apiService) // Point directly to a Cloud Run or VM instance
The configuration file (e.g. config/records.yaml) should contain a sequence of records matching this structure:
# config/records.yaml
- name: "@"
type: TXT
value: "v=spf1 include:_spf.google.com ~all"
- name: mail
type: A
value: 1.2.3.4
ttl: 600
- name: www
type: CNAME
value: lb.google.com
Cloud DNS API Reference
| Method | Type | Description | Default |
|---|---|---|---|
.record(filePath) |
string |
Bulk loads and appends a list of DNS records from a local .yaml, .yml, or .json file. |
- |
.record(name, type, value, ttl?) |
string, string, string, number |
Adds a single custom DNS record set to the zone. Target names and values are automatically appended with trailing dots, and TXT/SPF records are auto-quoted. |
- |
.pointer(name, target) |
string, BaseBuilder \| Output<string> \| string |
Resolves a cross-resource target endpoint (e.g. Cloud Run service URL or VM IP) at deploy-time, automatically converting A records to CNAME and stripping protocol headers for external domain integrations. |
- |
| --- |
Compute Engine VM
Provision and manage VM instances on Google Cloud Compute Engine with seamless OS booting, custom network configuration, public SSH key deployment, and universal playbook provisioning.
GCP.VM("my-app-server")
.machineType("e2-medium")
.zone("us-central1-a")
.image("projects/ubuntu-os-cloud/global/images/family/ubuntu-2204-lts")
.network("global/networks/default")
.sshKey("~/.ssh/id_ed25519.pub")
.provision("config/docker.yaml", "config/nginx.yaml")
Compute Engine VM API Reference
| Method | Type | Description | Default |
|---|---|---|---|
.machineType(type) |
string |
Machine tier to provision (e.g. "e2-micro", "e2-medium", "n2-standard-2"). |
"e2-micro" |
.image(img) |
string |
Boot image URI or family name. | "projects/ubuntu-os-cloud/global/images/family/ubuntu-2204-lts" |
.zone(z) |
string |
GCP availability zone where the VM instance resides. | "us-central1-a" |
.network(netPath) |
string |
Virtual Private Cloud network resource path. | "global/networks/default" |
.sshKey(keys) |
string \| string[] |
Public SSH key strings, or local paths to public keys (e.g. "~/.ssh/id_rsa.pub") to inject into the VM for SSH access. |
[] |
.sshUser(user) |
string |
SSH username used for provisioning. Falls back to GCP_SSH_USER env var → config.gcp.sshUser → "root". |
"root" |
.provision(...playbooks) |
string[] |
Ordered list of playbook configuration YAML files to execute on the VM post-boot. | [] |
.forceConfigCheck() |
void |
Forces Puls to ignore previously cached playbook hashes and re-run all provision playbooks. | - |
Outputs
ip(Output<string>): The resolved public/external IPv4 address assigned to the VM.id(Output<string>): The unique GCP Compute Engine instance identifier.
How Provisioning Works
- Stateless Configuration Caching: Puls keeps track of applied playbooks by storing their hashes under the instance's custom metadata key
puls-provision. This keeps configuration caching stateless and cloud-native, avoiding any separate database. - Idempotency: During deployment, Puls checks if the hashes of your local playbooks match the hashes in the VM metadata. If they match, playbook execution is safely skipped.
- Dynamic Stops and Resizes: If the
machineTypeconfiguration is modified, Puls will automatically stop the VM instance, apply the new sizing configuration (setSize), and restart the VM, returning it to a running state.
Golden Image & VM Templates
Puls supports declaring pre-baked Google Compute custom images, analogous to HashiCorp Packer's workflow. This allows developers to configure a custom golden image (with pre-installed packages, Docker, libraries, or security policies), bake it once, and then spin up actual GCP VMs from it in seconds.
GCP.Template
Define a custom GCP image template using the GCP.Template() helper:
const dockerBaseTemplate = GCP.Template("ubuntu-docker-base")
.baseImage("projects/ubuntu-os-cloud/global/images/family/ubuntu-2204-lts") // base image
.machineType("e2-medium")
.zone("us-central1-a")
.sshKey("~/.ssh/id_ed25519.pub")
.sshUser("ubuntu") // optional: SSH user for provisioning (default: "root")
.provision("playbooks/docker.yaml", "playbooks/security-hardening.yaml");
Baking Lifecycle:
- Idempotent Baking: Puls checks if a custom image with this name already exists in your GCP project. If it does and the provision playbooks match (via the applied hashes saved inside the image's description field), image baking is skipped.
- Delete & Rebuild: Because GCP images are read-only and immutable, if playbooks or configurations change, Puls will automatically delete the old custom image (
DELETE /compute/v1/projects/.../global/images/{name}) before rebuilding. - Automated VM Lifecycle: Puls creates a temporary instance (
puls-bake-temp-{name}), waits for SSH, runs the Ansible playbooks, stops the instance, creates a new custom image from the stopped instance's boot disk, writes the playbook hashes into the image's description field, waits until the image status isREADY, and deletes the temporary provisioning VM instance.
VM Cloning from Template (.fromTemplate())
Use the fluent .fromTemplate() builder method on GCP.VM() to spin up a server directly from a pre-baked custom image:
@Deploy({ parallel: true })
class ProductionCluster extends Stack {
// 1. Declare the template resource
dockerBase = GCP.Template("ubuntu-docker-base")
.baseImage("projects/ubuntu-os-cloud/global/images/family/ubuntu-2204-lts")
.sshKey("~/.ssh/id_ed25519.pub")
.provision("playbooks/docker.yaml");
// 2. Clone GCP VMs from the custom image concurrently in seconds
server1 = GCP.VM("prod-game-01").fromTemplate(this.dockerBase).machineType("e2-medium");
server2 = GCP.VM("prod-game-02").fromTemplate(this.dockerBase).machineType("e2-medium");
}
- Dynamic DAG Dependencies: Calling
.fromTemplate(template)automatically registers an implicit dependency. The parallel scheduling engine ensures that the custom image is fully baked and available before the GCP VM instances start spawning concurrently.
BigQuery View
Create and manage Google BigQuery views. Puls automatically manages view creation, query updates, and dependency resolution.
Implicit Dependency Resolution via Output Interpolation
Puls supports native dependency resolution and reference generation when authoring BigQuery views. When you interpolate a GCP.BigQueryView instance into another view's query template literal, Puls automatically:
- Registers an implicit dependency (
dependsOn) on the referenced view so they are created in the correct order. - Resolves the sentinel placeholder to the view's fully-qualified backtick-quoted reference at deploy time (e.g.
`project-id.dataset_id.view_name`).
const baseView = GCP.BigQueryView("base_users")
.dataset("analytics")
.query("SELECT * FROM `my-gcp-project.raw.users` WHERE active = true");
const ltvView = GCP.BigQueryView("user_ltv")
.dataset("analytics")
.query(`SELECT id, sum(ltv) as total_ltv FROM ${baseView} GROUP BY id`);
BigQuery View API Reference
| Method | Type | Description | Default |
|---|---|---|---|
.dataset(datasetId) |
string |
Required. The ID of the BigQuery dataset containing the view. | - |
.query(sql) |
string |
Required. The SQL query defining the view. Supports template-literal interpolation of other BigQueryView instances. |
- |
.useLegacySql(enabled) |
boolean |
Toggles whether the query uses legacy SQL syntax instead of standard SQL. | false |
Outputs
qualifiedRef(Output<string>): The fully-qualified, backtick-quoted identifier of the view (e.g.,`project-id.dataset_id.view_name`).qualifiedRef(synchronous getterview.qualifiedRef): A synchronous property returning the same backtick-quoted reference, useful for nesting inside strings or passing to other resources before deployment.
IAM (Service Accounts & Bindings)
Provision and manage custom Google Cloud Service Accounts (GCP.ServiceAccount) and project-level IAM bindings (GCP.IAMBinding) to automate secure credentials setup.
Service Account
Create a new Service Account cleanly.
const mySA = GCP.ServiceAccount("backup-agent")
.displayName("Backup Agent Service Account")
.description("Used for nightly database backup jobs");
Service Account API Reference
| Method | Type | Description | Default |
|---|---|---|---|
.displayName(name) |
string |
Sets the readable display name. | - |
.description(desc) |
string |
Sets the description. | - |
email(dynamically resolves to the fully-qualified service account email address, e.g.backup-agent@project.iam.gserviceaccount.com).
IAM Binding
Bind members safely to project-level roles with Optimistic Concurrency Control (ETag lock) and non-destructive merges.
GCP.IAMBinding("storage-admin-binding")
.role("roles/storage.admin")
.member(mySA) // Wire Service Account builder directly!
.member("user:bob@gmail.com")
IAM Binding API Reference
| Method | Type | Description | Default |
|---|---|---|---|
.role(name) |
string |
Required. The project-level IAM role to bind (e.g. roles/pubsub.publisher, roles/storage.admin). |
- |
.member(m) |
string \| GCPServiceAccountBuilder \| Output<string> |
Binds a single member. Strings are automatically prefixed with the correct type (e.g., serviceAccount: or user:). |
- |
.members(...m) |
Array<string \| GCPServiceAccountBuilder \| Output<string>> |
Binds multiple members. | - |
Complete Example
This example demonstrates deploying a secure stack consisting of a Cloud SQL database, a Secret Manager secret containing the database credentials, a Pub/Sub topic + push subscription, a Cloud Run API service, a custom Service Account with publisher rights, and a Cloud DNS zone that points a CNAME to the API service.
import "dotenv/config";
import "reflect-metadata";
import { Stack, Deploy, Config } from "@puls-dev/core";
import { GCP } from "@puls-dev/gcp";
Config.set({
providers: {
gcp: {
projectId: "prod-stack-101",
serviceAccountPath: "./gcp-key.json",
region: "us-east1",
},
},
});
@Deploy({ dryRun: false })
class CloudStack extends Stack {
// 1. Pub/Sub Topic for background tasks
eventsTopic = GCP.PubSub.Topic("background-tasks");
// 2. Secret Credentials
dbPassword = GCP.Secret("app-db-password")
.plainText("supersecurepassword123");
// 3. Private Postgres Database
db = GCP.CloudSQL("app-db")
.engine({ engine: "postgres", version: "16" })
.size("db-f1-micro")
.storage(20)
.credentials("app_user", this.dbPassword.resolvedValue ?? "fallbackpwd")
.database("production_db")
.publicAccess(false);
// 4. Public API Service
web = GCP.CloudRun("api-service")
.image("us-east1-docker.pkg.dev/prod-stack-101/images/api:latest")
.port(3000)
.cpu(1)
.memory(512)
.env({
NODE_ENV: "production",
DATABASE_HOST: this.db.endpoint,
DATABASE_PORT: this.db.port,
DATABASE_NAME: "production_db",
DATABASE_USER: "app_user",
DATABASE_PASS: this.dbPassword,
TOPIC_NAME: this.eventsTopic.resolvedTopicName ?? "unknown-topic",
})
.public(true);
// 5. Push Subscription sending Topic events directly back to API handler
pushSub = GCP.PubSub.Subscription("tasks-push")
.topic(this.eventsTopic)
.pushEndpoint("https://api-service-xyz.run.app/tasks/webhook")
.ackDeadline(15);
// 6. Custom Service Account for background workers
workerSA = GCP.ServiceAccount("background-worker")
.displayName("Background Worker")
.description("Service account with publisher permissions");
// 7. IAM Binding giving the Service Account publisher rights
pubBinding = GCP.IAMBinding("worker-pub-rights")
.role("roles/pubsub.publisher")
.member(this.workerSA);
// 8. Cloud DNS Zone pointing to the API service CNAME
dns = GCP.CloudDNS("mycompany.com")
.record("www", "CNAME", "lb.google.com")
.pointer("api", this.web);
}
Deploy your stack with:
Required Permissions
To manage Cloud Run, Cloud SQL, Secret Manager, Pub/Sub, Cloud DNS, and IAM resources, your service account key needs the following IAM roles:
- Cloud Run Admin (
roles/run.admin) - full control over Cloud Run services. - Service Account User (
roles/iam.serviceAccountUser) - to associate runtime service accounts. - Cloud SQL Admin (
roles/cloudsql.admin) - full control over Cloud SQL database instances. - Secret Manager Admin (
roles/secretmanager.admin) - full control over Secret Manager containers and values. - Pub/Sub Admin (
roles/pubsub.admin) - full control over Pub/Sub topics and subscriptions. - DNS Administrator (
roles/dns.admin) - full control over Google Cloud DNS zones and records. - Create/Delete Service Accounts (
roles/iam.serviceAccountAdmin) - to create, modify, and delete custom service accounts. - Project IAM Admin (
roles/resourcemanager.projectIamAdmin) - to set and modify project-level IAM policies.