# Google Cloud Run + Cloud SQL

> Install or upgrade a verified public release with the Cloud Run bundle: Cloud SQL, Secret Manager version pins, staged traffic, and the two Canvas handoffs.

[Canonical HTML page](https://safeonlineexam.com/docs/deploy/google-cloud)

This is the recommended managed deployment path. The versioned Cloud Run bundle
installs or upgrades a published image without an application source checkout or
Cloud Build. It uses one immutable image digest for the migration job, cleanup
job, and application service.

The scripts never print secret values. They pin Secret Manager to the exact
numbered versions in `.state/secret-versions.env`, run migrations before traffic
moves, and keep a candidate revision at zero traffic until `/ready` and the public
JWKS endpoint pass.

## Requirements [#requirements]

* A billed Google Cloud project and permission to administer Cloud Run, Cloud
  SQL, IAM, Secret Manager, Cloud Scheduler, and required APIs.
* Current `gcloud`, `gh`, Docker, `jq`, OpenSSL, and `curl` on the operator
  workstation.
* A final public HTTPS origin. The bundle can reserve a stable `run.app` URL;
  set `TOOL_URL` only when an approved custom HTTPS origin will front it.
* A Canvas administrator and a device-management owner for the two external
  handoffs.

## Download and verify the bundle [#download-and-verify-the-bundle]

Use the release notes for the current version and digest:

```bash
export VERSION="X.Y.Z"
curl -fLO "https://github.com/JSB2010/safe-online-exam/releases/download/v${VERSION}/safe-online-exam-${VERSION}-cloud-run.tar.gz"
curl -fLO "https://github.com/JSB2010/safe-online-exam/releases/download/v${VERSION}/safe-online-exam-${VERSION}-cloud-run.tar.gz.sha256"
sha256sum --check "safe-online-exam-${VERSION}-cloud-run.tar.gz.sha256"
tar -xzf "safe-online-exam-${VERSION}-cloud-run.tar.gz"
cd "safe-online-exam-${VERSION}-cloud-run"
```

Authenticate, then run the exact attestation command from the release notes. It
must identify `JSB2010/safe-online-exam`,
`.github/workflows/publish-release-image.yml`, the release commit, and the tag.

## Guided installation [#guided-installation]

Start with the bundle's own `README.md`, then run:

```bash
./setup.sh
```

The walkthrough performs a read-only preflight, collects a project/region/resource
prefix, generates protected bootstrap material, presents the Cloud SQL profile
catalog and cost confirmation, provisions or validates resources, and pauses for
Canvas at the right times. Sensitive values use no-echo prompts or protected files,
not command-line arguments.

Keep the extracted bundle, `BOOTSTRAP_DIRECTORY`,
`CLIENT_IDENTITY_DIRECTORY`, and `STATE_DIRECTORY` out of `/tmp`,
`/private/tmp`, and `TMPDIR`. The bundle rejects those locations so an operating
system cleanup policy cannot remove the only client identity or deployment record.
It writes generated secrets without a terminal line ending and rejects a supplied
database-password file that contains a line break.

It is safe to resume by stage when different administrators own different parts:

```bash
./setup.sh --stage configure
./setup.sh --stage prepare
./setup.sh --stage install
./setup.sh --stage finalize
```

For unattended use, follow the exact `--non-interactive` file-input examples in
the extracted README. Do not turn secrets into shell arguments.

## What a new installation creates [#what-a-new-installation-creates]

With `RESOURCE_NAME=safe-online-exam`, defaults are:

| Resource                                 | Default                                                |
| ---------------------------------------- | ------------------------------------------------------ |
| Cloud Run service and Cloud SQL instance | `safe-online-exam`                                     |
| Migration and cleanup jobs               | `safe-online-exam-migrate`, `safe-online-exam-cleanup` |
| Runtime and scheduler service accounts   | `safe-online-exam`, `safe-online-exam-sched`           |
| PostgreSQL database and user             | `safe_online_exam`                                     |
| Secret Manager prefix                    | `safe_online_exam_`                                    |

Use a short, school-specific suffix for separate installations in one project.
Once Canvas is configured, keep the service name, project, region, and public URL
stable. These portable defaults do not rename the repository's existing
`canvas-seb-dev` or `canvas-seb-prod` source-build environments.

## Cloud SQL and public access [#cloud-sql-and-public-access]

`production-zonal` is the bundle's cost-conscious new-install default: Cloud SQL
PostgreSQL 17 Enterprise, one dedicated vCPU, 3.75 GiB, one zone, daily backups,
14 retained backups, seven-day PITR, storage auto-growth, deletion protection, and
connector-only access. It trades automatic cross-zone failover and the HA SLA for
a lower bill. Use `./prepare.sh --list-cloud-sql-profiles` before accepting a
billable resource, and check current Google Cloud pricing and terms.

`existing-reviewed` never creates an instance; the institution owns its regional,
HA, backup, retention, networking, capacity, and deletion-protection review.

On a newly configured project, the bundle waits with bounded retries for the Cloud
SQL Admin API to propagate and for a new instance to become `RUNNABLE`. If either
wait expires, inspect the existing Google Cloud operation before rerunning
`prepare.sh`; do not assume a second create request is safe.

Canvas must reach the tool without a Google login. `PUBLIC_ACCESS=true` grants
`roles/run.invoker` to `allUsers` on the application service only. If policy
forbids it, put an approved public HTTPS load balancer in front, set `TOOL_URL` to
that stable origin, and keep migration and cleanup jobs non-public.

## The two Canvas handoffs [#the-two-canvas-handoffs]

After the URL is reserved, the installer stops for the Canvas API OAuth Developer
Key. It then installs a first revision with `bootstrap-pending` LTI values. Create
the LTI key using `${TOOL_URL}/lti/config`, install the app, and run the final
stage with the actual client and deployment IDs. The final stage creates new
numbered versions only for those LTI identifiers, updates jobs, stages a revision,
checks it, and explicitly cuts traffic over.

Do not upload the generated SEB `.p12`, private key, or password to Cloud Run or
Secret Manager. Move the client-only identity directory into the approved MDM or
vault workflow.

## Custom domain and generated Cloud Run URL [#custom-domain-and-generated-cloud-run-url]

When the final public origin uses Cloud Run domain mapping, set `TOOL_URL` to that
HTTPS origin before the prepare stage. After the service is prepared, run:

```bash
./map-domain.sh cloudrun.env
```

The helper creates or inspects the mapping and reports its DNS records and Ready
conditions; it does not change external DNS. Complete domain ownership and DNS,
then verify the custom origin's health, readiness, JWKS, and LTI endpoints.
Cloud Run domain mapping is a Preview feature, so use an approved load balancer
when that is not an acceptable operational dependency.

Leave the generated `run.app` URL enabled while the installation is finalized.
Set `DISABLE_DEFAULT_URL_AFTER_FINALIZE=true` only after the custom origin passes
those checks. The final stage verifies its zero-traffic candidate first, cuts over,
rechecks the custom origin, and then disables the generated URL. Later upgrades
preserve that policy: they verify the custom origin, temporarily restore the
generated URL for tagged candidate checks, cut over, and disable it again even if
the candidate exits early.

## Generate and upload the Canvas theme loader [#generate-and-upload-the-canvas-theme-loader]

After `prepare.sh`, have the bundle render the exact loader for the configured
public origin:

```bash
./canvas-theme-loader.sh cloudrun.env
```

Upload the generated `canvas-theme-loader.js` as the active Canvas account or
sub-account theme's **desktop JavaScript** before testing a protected assessment.
It contains no secret material. This theme step is separate from LTI registration;
see [Load the detector script](/docs/canvas/detector-script) for the Canvas-side
procedure.

## Upgrade and rollback [#upgrade-and-rollback]

Download and verify the next bundle. Preserve `cloudrun.env`, the bootstrap
directory, `.state`, and the client identity records; merge new template keys and
replace only `APP_VERSION` and `APP_IMAGE` with the new bundle values.

```bash
./upgrade.sh cloudrun.env
```

The helper creates an on-demand Cloud SQL backup, waits for its operation to
finish, and requires the backup status to be `SUCCESSFUL` before migrations or
traffic changes. It records the prior traffic revision, migrates, updates cleanup,
deploys a no-traffic candidate, checks `/ready` and JWKS, then moves 100% traffic.
When the generated URL was disabled, it verifies the custom origin, temporarily
enables that URL for the candidate, then verifies the custom origin again and
restores the disabled-URL policy. If a candidate check fails, the prior revision
keeps traffic and guarded cleanup restores the prior URL policy; completed forward
migrations remain.

Application rollback needs the protected rollback record and an explicit
schema-compatibility confirmation. It verifies `TOOL_URL` when a custom origin is
configured. Restore a backup into a controlled target for schema or data recovery;
never down-migrate or overwrite the active database as a first response.

## Post-deployment checks [#post-deployment-checks]

```bash
curl -fsS "$TOOL_URL/health"
curl -fsS "$TOOL_URL/ready"
curl -fsS "$TOOL_URL/.well-known/jwks.json"
curl -fsS "$TOOL_URL/lti/config"
curl -fsS "$TOOL_URL/js/canvas-seb-detector.js"
```

Also inspect the active revision and digest, migration and cleanup executions,
Cloud Scheduler, backup/PITR state, numbered secret references, and application
errors. These route checks do not replace the role-based
[Canvas and SEB acceptance run](/docs/operate).