The starting point.

The on-premise platform combined a Vue interface, two Spring Boot services, a Python Temporal worker, Temporal, Keycloak, PostgreSQL and MinIO. It handled document extraction and AI-assisted workflows, but environments did not share one dependable setup path.

Calling this a cloud migration would only describe the destination. The actual job was to move stateful and stateless components deliberately, separate demonstration delivery from production delivery, and give operators a controlled switch between two production slots.

I mapped the change as four connected boundaries: workload packaging, managed data, environment configuration and release control. That prevented “put it on Kubernetes” from becoming the whole design.

What the design had to solve.

The new platform model needed to make five things explicit:

  1. Vue, Spring Boot, Python, Temporal and Keycloak workloads needed one Kubernetes deployment model.
  2. PostgreSQL and MinIO data responsibilities had to move to Cloud SQL and Cloud Storage without hiding the change behind containers.
  3. Reusable infrastructure had to stay common while environment-specific values remained reviewable inputs.
  4. A demonstration pipeline and the production release path needed different controls because they carried different operational risk.
  5. Production rollout required an inactive slot, smoke verification and an explicit manual switch.

The diagnosis.

The fragile part was not a single service. It was the path from source code to a running environment. When that path varies by environment, every change carries hidden assumptions.

A direct lift-and-shift would have reproduced those assumptions in a new location. Running PostgreSQL and MinIO inside the new cluster would also have preserved operational responsibilities that Google Cloud could own at clearer managed boundaries.

I therefore treated reproducibility and reversibility as the central requirements. Terraform modules define the Google Cloud foundation, Helm packages the Kubernetes workloads, and environment values stay separate from reusable definitions.

The resulting platform model.

GitLab CI starts the production path, Cloud Build creates the deployable images, and Artifact Registry holds the version that reaches GKE. Deployment targets the inactive blue/red slot. Kubernetes readiness and a smoke test must pass before an operator switches traffic. Cloud SQL and Cloud Storage replace the self-managed PostgreSQL and MinIO responsibilities.

The resulting platform model. Build once, verify the inactive slot, then make the production switch explicit.SYSTEM VIEW / CLOUDGITLAB CICLOUD BUILDARTIFACTREGISTRYGKE BLUE / REDCLOUD SQL + GCSVERSIONED DELIVERY PATH
Build once, verify the inactive slot, then make the production switch explicit.

Why these technologies fit the migration.

These were choices inside an existing product and an agreed Google Cloud destination, not a greenfield ranking of every possible platform. The useful question was which boundaries made this particular mix of workloads repeatable and operable.

T01 The choice

GKE as the common workload boundary

Why
The platform already combined a Vue interface, Spring Boot services, a Python worker, Temporal and Keycloak. GKE gave those different workloads one deployment, readiness and blue/red slot model while managed data moved outside the cluster.
What I ruled out
Splitting the components across serverless products would have imposed several execution models on one release path, while long-lived VMs would have preserved more host-specific setup and maintenance.
What it cost
The team accepts a Kubernetes control surface, cluster lifecycle work and the need to keep workload requests, readiness and upgrades explicit.
T02 The choice

Terraform modules for the Google Cloud foundation

Why
Reusable modules kept common infrastructure definitions together while exposing environment differences as reviewable inputs. That directly addressed the environment-specific setup the migration needed to remove.
What I ruled out
Console configuration or copied templates could create the first environment quickly, but repeated changes would be harder to review and each copy could drift independently.
What it cost
Module contracts, provider state and version changes require discipline; even a small infrastructure exception must be modelled rather than fixed silently by hand.
T03 The choice

Helm for Kubernetes workload packaging

Why
Helm provided a release unit for the related Kubernetes resources and a controlled place for environment values without copying the full workload definition for every slot.
What I ruled out
Raw manifests would be more direct for a single deployment, but duplicating them across environments and blue/red slots would make shared changes and intentional differences harder to distinguish.
What it cost
Templates and values introduce indirection, so rendered output must be inspected and chart changes versioned with the same care as application code.
T04 The choice

Separate Cloud Build and GitLab delivery responsibilities

Why
Cloud Build produced images in the Google Cloud path, while GitLab CI kept production orchestration, verification and the explicit traffic decision. The split matched the different consequences of building an artifact and releasing it.
What I ruled out
One undifferentiated deploy pipeline would be simpler to describe, but it would blur demonstration and production controls and make artifact creation, rollout and traffic switching one coupled action.
What it cost
Two systems require credentials, artifact hand-offs and failure diagnosis across a boundary, and their contracts must remain aligned.

Operating rules for a reversible release.

The stack defines the route; these rules define who owns state, what counts as evidence and how production can change without becoming an irreversible leap.

D01

Turn environment variance into a review surface

Before a release, environment-specific values and stateful responsibilities are visible together, including who owns data, backup and recovery. A difference becomes an explicit input or hand-off, not an invisible fix in a running environment.

The trade-offReviewing variance and ownership adds preparation, but prevents configuration drift or unowned recovery work from emerging during cutover.

D02

Verify the candidate end to end

A release is followed from the built image into the inactive environment, then checked with readiness, infrastructure signals and a smoke test. No single green indicator stands in for the whole path.

The trade-offThis takes longer than accepting a successful build as proof; the smoke test is still deliberately scoped and does not claim to cover every business workflow.

D03

Separate artifact evidence from traffic authority

A successful build creates a candidate; it does not grant production access. The same artifact is deployed and exercised on the inactive blue/red slot, while the decision to move traffic remains a distinct operator action.

The trade-offThis governance adds hand-offs and requires both slots to stay ready, but prevents build success from being mistaken for release approval.

D04

Make cutover explicit and reversible

Traffic moves only after the checks are visible and an operator approves the switch. The previous slot remains the clear return path instead of being overwritten in place.

The trade-offThe gate adds a deliberate pause, but keeps final production authority and reversal visible when evidence is incomplete.

Delivery and verification.

I ordered the work around dependency and reversibility: establish the Google Cloud foundation, package the workloads, externalise environment values, move data responsibilities, then exercise the release path before switching production traffic.

The demonstration and production paths stay distinct. Production moves from GitLab CI through Cloud Build and Artifact Registry to the inactive GKE environment instead of deploying directly over the live slot.

Verification combines Kubernetes readiness, Google Cloud alerts and infrastructure signals with a smoke test before the switch. This does not prove every business workflow. It establishes that the release candidate is healthy enough for a controlled cutover.

The delivered result.

The migration replaced an on-premise, environment-specific route with reusable infrastructure modules, packaged workloads, managed data services and two controlled production slots.

The useful outcome is not simply “running on Google Cloud.” It is a release path that can be reviewed before deployment, checked before traffic moves and repeated for the next environment.

I do not attach an invented speed or reliability percentage to that result. The defensible change is structural: fewer environment-specific assumptions, explicit operational ownership and a reversible production decision.

What this case can prove

The organisation, product name, endpoints, storage configuration, costs, workload volumes and delivery dates are omitted. Component boundaries and the release sequence come from the project record.