Deploying GenMedia Creative Studio
Deploying GenMedia Creative Studio
Section titled “Deploying GenMedia Creative Studio”Deployment of GenMedia Creative Studio is accomplished using a combination of Terraform and Cloud Build. Terraform is used to deploy the infrastructure and Cloud Build is used to create the container image and update the Cloud Run service to use it.
You have two deployment options for this application:
- Deploy using a custom domain. Use this if:
- You need to support external identities. Included Terraform script does not support this; however, you can customize the script.
- You prefer more control over the domain used
- Deploy using the autogenerated Cloud Run Domain. Use this if:
- You can not create a DNS entry
- IAP for Cloud Run Known Limitations are non-blockers (e.g., no external identities, no Cloud CDN support)
Prerequisites
Section titled “Prerequisites”You’ll need the following
- An existing Google Cloud Project
- If you want to use a custom domain, you need the ability to create a DNS A record for your target domain that resolves to the provisioned load balancer
- For the deploy step, the operator/principal running
deploy.shorbuild.shneedsroles/run.developer(or a superset such asroles/run.admin) androles/iam.serviceAccountUser(to act as the runtime service account). The deploy now runs as the caller —cloudbuild.yamlonly builds and pushes the image, so the caller (not the Cloud Build service account) performsgcloud run deploy. - To use
deploy.sh list-versionsor to deploy by version/digest (--version/--image), the caller also needsroles/artifactregistry.readeron thecreative-studiorepository (to runartifacts docker images listand resolve digests). Terraform codifies this grant for the principal(s) indeployer_members(theartifact-registrymodule; dormant when the list is empty).
1. Download the source code for this project
Section titled “1. Download the source code for this project”Download the source
git clone https://github.com/GoogleCloudPlatform/genmedia-creative-studio.git2. Export Environment Variables
Section titled “2. Export Environment Variables”The following environment variables are the minimum required to deploy the application.
- REGION - Should be set to
us-central1. Prior to selecting a different region, validate the GenAI models needed are available here. - PROJECT_ID - Set to the desired Google Cloud project’s ID, obtained via
gcloudbelow or you can enter it manually. - DOMAIN_NAME - Update with the DNS name to be used to reach the web application (e.g., creativestudio.example.com). A Google Cloud Managed certificate will be created for this domain.
- INITIAL_USER - Email address of initial user given access to the web application (e.g., admin@example.com)
Replace the example values and execute the script below:
export REGION=us-central1 PROJECT_ID=$(gcloud config get project)export INITIAL_USER=admin@example.comDeploying with Custom Domain
Section titled “Deploying with Custom Domain”Follow these steps if you are going to deploy GenMedia Creative Studio using your own custom domain. You will need the ability to create a DNS A record if you choose this deployment option.
1. Initialize Terraform
Section titled “1. Initialize Terraform”Because you are using a custom domain, you will need to export one more variable with the DNS name for the domain that will be used to navigate to GenMedia Creative Studio.
export DOMAIN_NAME=creativestudio.example.comMake sure your command line is in the Cloud Run Terraform root, deploy/terraform/cloudrun. Then create the terraform.tfvars using the following command:
cd deploy/terraform/cloudruncat > terraform.tfvars << EOFproject_id = "$PROJECT_ID"initial_user = "$INITIAL_USER"domain = "$DOMAIN_NAME"EOF
terraform initterraform apply2. Create a DNS A record for the domain name
Section titled “2. Create a DNS A record for the domain name”A load balancer and a Google Cloud managed certificate are provisioned by the Terraform configuration file. You must create a DNS A record that resolves to the IP address of the provisioned load balancer. Below is a sample output from running the terraform apply command, showing where the provisioned application balancer’s IP is displayed.
If you use Google Cloud DNS, follow the steps here. Provisioning a Google-managed certificate might take up to 60 minutes from the moment your DNS and load balancer configuration changes have propagated across the internet.
If you take too long to create the A record, usually >15 minutes or the DNS entry resolves to any other IP address than the load balancer’s, provisioning of the Google Cloud Managed certificate may fail with a status of
FAILED_NOT_VISIBLE. If this is the case, make sure the DNS A record is updated correctly and follow the steps here.
3. Build and Deploy Container Image
Section titled “3. Build and Deploy Container Image”A shell script, build.sh, is included at the repository root that submits a build to Cloud Build to build and push the application’s container image, then deploys it to Cloud Run (the deploy is run by build.sh as the caller; cloudbuild.yaml only builds and pushes). Run it from the repository root:
cd - # back to the repository root (if you ran terraform from deploy/terraform/cloudrun)./build.sh4. Wait for certificate to go to provisioned state
Section titled “4. Wait for certificate to go to provisioned state”With both the infrastructure and application deployed, you are just waiting for the certificate to complete provisioning. Once you see the status as “ACTIVE” and the “In use by” section populated (see sample below), your application is ready for use. You can navigate to the Certificate Manager GCP Console page, and select the certificate to keep an eye on the status.
Deploying using Cloud Run Domain
Section titled “Deploying using Cloud Run Domain”If you are unable to create a DNS record in your corporate domain, you can also use the autogenerated Cloud Run domain along with it’s preview support for IAP to secure the endpoint.
Currently, Cloud Run’s integration with IAP is a preview feature and is subject to the “Pre-GA Offerings Terms” in the General Service Terms section of the Service Specific Terms. Pre-GA features are available “as is” and might have limited support. For more information, see the launch stage descriptions.
1. Initialize Terraform
Section titled “1. Initialize Terraform”Make sure your command line is in the Cloud Run Terraform root, deploy/terraform/cloudrun. Then create the terraform.tfvars using the following command:
cd deploy/terraform/cloudruncat > terraform.tfvars << EOFproject_id = "$PROJECT_ID"initial_user = "$INITIAL_USER"use_lb = falseEOF
terraform initterraform applyMake sure to take note of the Cloud Run URL that is output. This is what you will navigate to in your browser to access the application. Before doing that though, you need to build and deploy the container image.
2. Build and Deploy Container Image
Section titled “2. Build and Deploy Container Image”A shell script, build.sh, is included at the repository root that submits a build to Cloud Build to build and push the application’s container image, then deploys it to Cloud Run (the deploy is run by build.sh as the caller; cloudbuild.yaml only builds and pushes). Run it from the repository root:
cd - # back to the repository root (if you ran terraform from deploy/terraform/cloudrun)./build.sh3. Edit Cloud Run’s IAP Policy to provide initial user’s access
Section titled “3. Edit Cloud Run’s IAP Policy to provide initial user’s access”The last step is to change the IAP policy of the Cloud Run service to provide access to a user. You can also use a group but for the purposes of this example, a single user is given access.
gcloud beta iap web add-iam-policy-binding \--project=$PROJECT_ID \--region=$REGION \--member=user:$INITIAL_USER \--role=roles/iap.httpsResourceAccessor \--resource-type=cloud-run \--service=creative-studioCongratulations, you can now navigate to the address provided in the cloud-run-app-url Terraform output.
Deploying to Cloud Shell for Testing
Section titled “Deploying to Cloud Shell for Testing”Use this option if you want to quickly run the UI without having to setup a local development environment. To get started, use Cloud Shell and follow the tutorial instructions.
Updating GenMedia Creative Studio
Section titled “Updating GenMedia Creative Studio”As new features and fixes are added to GenMedia Creative Studio, you will want to update your deployment. You do not need to destroy your existing infrastructure.
Updating Application Code
Section titled “Updating Application Code”If you only need to update the application code (Python files, UI changes):
-
Pull the latest changes from the repository:
Terminal window git pull -
Run the build script:
Terminal window ./build.sh
This script submits a new build to Cloud Build to build and push a new container image, then deploys it (as the caller) to update the existing Cloud Run service.
Updating Infrastructure
Section titled “Updating Infrastructure”If the updates include changes to the Terraform configuration (e.g., new environment variables, new Google Cloud services):
-
Pull the latest changes:
Terminal window git pull -
Initialize Terraform to download any new provider requirements (run from the Cloud Run Terraform root):
Terminal window cd deploy/terraform/cloudrunterraform init -upgrade -
Apply the changes. Terraform will only update what has changed:
Terminal window terraform apply
Artifact Registry retention (cleanup policies)
Section titled “Artifact Registry retention (cleanup policies)”The artifact-registry module defines two cleanup policies on the creative-studio
repository: KEEP the most-recent cleanup_keep_count versions (default 20) and
DELETE untagged artifacts older than cleanup_untagged_older_than (default
2592000s = 30 days). Tagged versions (:latest, v…) are not affected by the
delete policy.
These ship with cleanup_policy_dry_run = true, so Artifact Registry only
logs what the policies would delete and deletes nothing. Review that dry-run
output (in the repository’s cleanup-policy logs), tune cleanup_keep_count /
cleanup_untagged_older_than if needed, then set cleanup_policy_dry_run = false on
a later terraform apply to enable real deletion. Repository-wide immutable tags are
intentionally not enabled (that would forbid re-pointing the moving :latest);
version-tag immutability is a convention, not an enforced repo setting.
Adding Additional Users
Section titled “Adding Additional Users”With any of the deployment options above that use IAP, if you need to add additional users, there are two steps to take to make sure those users can both access the application and the images generated:
- Application Access - Add the user to IAP. Follow these steps if you deployed using a load balancer, granting the user the IAP-Secured Web App User role. If you deployed using only the Cloud Run provided URL, follow these steps.
- Image Access - The images are served using the authenticated GCS URL of each storage object so users need to be granted the Storage Object Viewer role. The name of the bucket is available as the
assets-bucketTerraform output.
Note: For the application to function correctly, the Cloud Run service account must have the
Storage Object Viewer(roles/storage.objectViewer) role on the GCS bucket. This allows the application to read media assets and serve them to users through the proxy.
Fast redeploy + pre/post-flight checks (deploy.sh)
Section titled “Fast redeploy + pre/post-flight checks (deploy.sh)”deploy.sh (at deploy/scripts/deploy.sh) is a lightweight, non-Terraform operator loop for the
Cloud Run path. It is for redeploying the application to an environment that
Terraform has already provisioned — it is a deploy loop plus a pre-flight
sanity gate, not an infrastructure provisioner. Use build.sh/Terraform for the
provisioning workflow above; use deploy.sh for routine app redeploys and as a
cheap, CI-usable prerequisite gate.
It (1) verifies the environment’s prerequisites (23 pre-checks), (2) drives the
build+push via cloudbuild.yaml and then a caller-run gcloud run deploy
(cloudbuild.yaml builds and pushes only; it does not deploy), and (3) runs
post-deploy health and auth-wiring smoke checks.
# Run ALL pre-checks and exit WITHOUT deploying (safe/read-only; ideal for CI):./deploy/scripts/deploy.sh check
# Pre-checks -> build + deploy -> post-checks:./deploy/scripts/deploy.sh deploy
# Promote every WARN pre-check to a HARD-BLOCK (strict CI gate):./deploy/scripts/deploy.sh check --strict
# Deploy an already-built image without rebuilding (makes the "image exists"# check a HARD-BLOCK):./deploy/scripts/deploy.sh deploy --no-build --tag <existing-tag>
# List the image versions available in Artifact Registry (read-only), newest first:./deploy/scripts/deploy.sh list-versions
# Deploy a prior immutable version without rebuilding (rollback):./deploy/scripts/deploy.sh deploy --version v20260920t153012z-3eb17bf
# Deploy an exact image by digest (tag-independent ground truth):./deploy/scripts/deploy.sh deploy --image sha256:<digest>Common flags: --project <id>, --region <region>, --service <name>,
--tag <tag>, --version <tag>, --image <digest>. The project resolves from --project, then the PROJECT_ID env
var, then gcloud config. The region resolves from --region, then the REGION
env var, then GOOGLE_CLOUD_REGION, then gcloud config, defaulting to
us-central1. Run ./deploy/scripts/deploy.sh --help
for the full list, including the optional LB_HOST, IAP_ID_TOKEN, APP_ENV,
TF_STATE_BUCKET, and SECRET_ENV environment overrides.
Image versioning, listing, and rollback
Section titled “Image versioning, listing, and rollback”Every deploy (and build.sh) build now pushes two tags to the same image
digest:
-
an immutable version tag
v<UTC-timestamp>-<gitShortSHA>(for examplev20260920t153012z-3eb17bf) — human-sortable by time and tied to the source commit; by convention av…tag is never re-pushed, so it is a stable handle for a specific build. The tag is computed in three cases so the commit SHA is kept for provenance whenever one exists:- clean git checkout:
v<UTC-timestamp>-<gitShortSHA>(e.g.v20260920t153012z-3eb17bf). - dirty working tree:
v<UTC-timestamp>-<gitShortSHA>-dirty(e.g.v20260920t153012z-3eb17bf-dirty) — keeps the SHA and flags the uncommitted state. - true non-git (no repo / no resolvable HEAD):
v<UTC-timestamp>-nogit(e.g.v20260920t153012z-nogit).
A version tag is always produced.
- clean git checkout:
-
the moving
:latesttag — unchanged default; a plaindeploystill deploys:latest.
The container image is content-addressable by digest (@sha256:…) regardless
of tag; tags are a convenient index over immutable digests, and deploy-by-digest is
always the ground truth.
List what’s available (read-only; needs roles/artifactregistry.reader):
./deploy/scripts/deploy.sh list-versionsThis prints each version’s digest, tag(s), and create time, newest first.
Deploy a specific image without rebuilding. Both flags imply --no-build, and
the requested tag/digest must already exist (gated by pre-check #17, a HARD-BLOCK if
absent):
# by version tag./deploy/scripts/deploy.sh deploy --version v20260918t094412z-1a2b3c4
# by exact digest (a bare sha256:… or a full …/creative-studio@sha256:… ref)./deploy/scripts/deploy.sh deploy --image sha256:<digest>Rollback is just a redeploy of a prior image — there is no separate verb: run
list-versions, pick a prior v… tag (or @sha256 digest), then
deploy --version <prior-tag> (or --image <digest>). This deploys as the caller
(image-only, preserving env/runtime-SA/IAP) and runs the standard post-checks. For
reverting to a config that is already a Cloud Run revision, the faster path is a
Cloud Run revision rollback (gcloud run services update-traffic <svc> --to-revisions <rev>=100); use version/digest redeploy when the target image is not
a current revision.
Exit codes
Section titled “Exit codes”| Code | Meaning |
|---|---|
0 |
Success — all HARD-BLOCK pre-checks (and, in deploy mode, all post-checks) passed. |
1 |
Usage / internal error. |
2 |
A HARD-BLOCK pre-check failed — the deploy was refused. |
3 |
The build or gcloud run deploy step failed. |
4 |
The deploy succeeded but a post-deploy check failed. |
Each pre-check prints a PASS / WARN / BLOCK / SKIP line. A HARD-BLOCK
means the deploy would fail or the app would be broken/insecure at startup (e.g.
missing API, service account, IAM role, Firestore DB, or bucket). A WARN flags
a feature-degradation or an unrecommended-but-functional posture (e.g. a missing
Cloud Tasks queue only degrades async thumbnails); --strict promotes every WARN
to a HARD-BLOCK. One check — the Artifact Registry repository (#16) — is
auto-remediated (idempotent describe-then-create) in deploy mode only.
When building (i.e. not --no-build), two adjacent checks cover Cloud Build: #10
verifies the build service account’s roles, and #10a verifies the invoking
principal running deploy.sh can actually submit a build
(cloudbuild.builds.create) — a caller can pass #10 yet still hit
PERMISSION_DENIED on gcloud builds submit. #10a confirms the capability with a
positive testIamPermissions probe (Cloud Resource Manager), which reports the
permission even when it is granted via a Google group or a custom role; it falls
back to role-name matching only if that probe cannot return an answer. #10a
HARD-BLOCKs in deploy mode and WARNs in check-only mode, naming the exact role
and grant command.
Post-deploy checks
Section titled “Post-deploy checks”deploy mode polls /healthz and /readyz until they return 200 (or timeout),
runs an auth-wiring smoke test (a protected path must return 401/redirect
without a trusted identity, proving auth is enforced; a 200 with an
IAP_ID_TOKEN where one can be minted), and confirms the new revision is serving
100% of traffic. Any post-check failure exits non-zero (4).
Required-API single source
Section titled “Required-API single source”The required-API list is not hand-copied into the script. deploy.sh reads it
from apis.txt alongside the script (deploy/scripts/apis.txt, the single
machine-readable source), and pre-check #2a guards against drift by asserting
apis.txt matches the Terraform-declared set (activate_apis default in
deploy/terraform/modules/project-services/variables.tf). Keep the two in sync; if they diverge,
#2a warns. (Wiring Terraform to consume apis.txt directly, so both read one
file, is deferred to a Terraform phase where a zero-diff terraform plan gate can
prove the change is behaviour-neutral.)
What deploy.sh does NOT do
Section titled “What deploy.sh does NOT do”- It does not provision infrastructure — that is Terraform’s job. Its only creation is the idempotent Artifact Registry repo auto-remediation (#16).
- It does not manage the container-image contract beyond invoking the existing
build (preserving Terraform’s
ignore_changeson the image). - It does not read or write secret values. It only checks that referenced Secret Manager secrets exist (pre-check #19); it never becomes the secret store.
Rollback: delete deploy/scripts/deploy.sh — it provisions nothing, so removing it has no
infrastructure impact.