Investigation Report: Temporal Worker Deploy Pin on GCE VMs
Ticket ID: ENG-463
Title: fix: investigate stale-image deploy pin on GCE temporal-worker VMs
Date: June 1, 2026
Status: Closed (Won’t Fix / Already Correct)
Executive Summary
Section titled “Executive Summary”The Cloud Build deploy-temporal-workers task (cloudbuild-workers.yaml) executes an SSH command on the GCE VMs to deploy new builds of temporal-worker and ts-temporal-worker. It was hypothesized that the systemd unit files on the VMs might pin a static image SHA, meaning that restarts of the systemd unit would not pick up new changes if the underlying image tags were updated.
An empirical investigation was conducted on both the Staging and Production temporal worker VMs. The findings show that:
- No static SHA pinning is present in the systemd unit files.
- The systemd unit files are configured to run containers using the dynamic
latest-staging(for staging) andlatest-main(for production) tags. - The deployment process correctly handles updating the local image tag mapping (
:COMMIT_SHAtagged as:latest-${BRANCH_NAME}) prior to restarting the systemd unit. - The Ansible playbooks are templated to automatically use environment-appropriate tags (
latest-staging/latest-main) instead of hardcoded SHAs.
Accordingly, the VM systemd units and Ansible templates were verified to be functioning exactly as designed. The only change introduced by this PR is a VM-routing update in cloudbuild-workers.yaml so the deploy-temporal-workers step targets erp-staging-temporal-workers for staging and erp-prod-temporal-workers for main (removing the prior skip-on-main guard, which is no longer needed now that both VMs’ systemd units already reference the correct dynamic tags). Aside from that pipeline routing change, no further fixes to the deployment pipeline or VM configuration are required.
Detailed Investigation Findings
Section titled “Detailed Investigation Findings”1. Verification on erp-staging-temporal-workers VM
Section titled “1. Verification on erp-staging-temporal-workers VM”We successfully established an IAP SSH session to erp-staging-temporal-workers and inspected the systemd unit configurations for both temporal-worker and ts-temporal-worker:
Service definition for temporal-worker:
Section titled “Service definition for temporal-worker:”[Unit]Description=Temporal WorkerRequires=docker.serviceAfter=docker.service
[Service]Type=simpleRestart=alwaysRestartSec=10ExecStartPre=-/usr/bin/docker rm -f temporal-workerExecStart=/usr/bin/docker run --rm \ --name temporal-worker \ --env-file /opt/temporal-worker/.env \ us-east1-docker.pkg.dev/ordermatic-prod/erp-unlocked/temporal-worker:latest-stagingExecStop=/usr/bin/docker stop temporal-workerTimeoutStartSec=120
[Install]WantedBy=multi-user.targetService definition for ts-temporal-worker:
Section titled “Service definition for ts-temporal-worker:”[Unit]Description=TS Temporal WorkerRequires=docker.serviceAfter=docker.service
[Service]Type=simpleRestart=alwaysRestartSec=10ExecStartPre=-/usr/bin/docker rm -f ts-temporal-workerExecStart=/usr/bin/docker run --rm \ --name ts-temporal-worker \ --env-file /opt/ts-temporal-worker/.env \ us-east1-docker.pkg.dev/ordermatic-prod/erp-unlocked/ts-temporal-worker:latest-stagingExecStop=/usr/bin/docker stop ts-temporal-workerTimeoutStartSec=120
[Install]WantedBy=multi-user.targetRunning Docker Container Verification:
Section titled “Running Docker Container Verification:”Inspecting the active containers confirmed they are indeed running using the latest-staging tag as intended:
/temporal-worker:us-east1-docker.pkg.dev/ordermatic-prod/erp-unlocked/temporal-worker:latest-staging/ts-temporal-worker:us-east1-docker.pkg.dev/ordermatic-prod/erp-unlocked/ts-temporal-worker:latest-staging
2. Verification on erp-prod-temporal-workers VM
Section titled “2. Verification on erp-prod-temporal-workers VM”We similarly SSH’d into erp-prod-temporal-workers and inspected the production services:
Service definition for temporal-worker:
Section titled “Service definition for temporal-worker:”...ExecStart=/usr/bin/docker run --rm \ --name temporal-worker \ --env-file /opt/temporal-worker/.env \ us-east1-docker.pkg.dev/ordermatic-prod/erp-unlocked/temporal-worker:latest-main...Service definition for ts-temporal-worker:
Section titled “Service definition for ts-temporal-worker:”...ExecStart=/usr/bin/docker run --rm \ --name ts-temporal-worker \ --env-file /opt/ts-temporal-worker/.env \ us-east1-docker.pkg.dev/ordermatic-prod/erp-unlocked/ts-temporal-worker:latest-main...Running Docker Container Verification:
Section titled “Running Docker Container Verification:”Active containers on the production VM are running:
/temporal-worker:us-east1-docker.pkg.dev/ordermatic-prod/erp-unlocked/temporal-worker:latest-main/ts-temporal-worker:us-east1-docker.pkg.dev/ordermatic-prod/erp-unlocked/ts-temporal-worker:latest-main
3. Infrastructure as Code / Configuration Audit
Section titled “3. Infrastructure as Code / Configuration Audit”We verified the Ansible playbook infrastructure-gcp/ansible/playbooks/temporal-workers.yml which handles the deployment/provisioning of the temporal-workers service.
Lines 23-24 define:
temporal_worker_image_tag: "{{ (app_environment == 'production') | ternary('latest-main', 'latest-staging') }}"ts_temporal_worker_image_tag: "{{ (app_environment == 'production') | ternary('latest-main', 'latest-staging') }}"The systemd files generated by Ansible template are written as:
ExecStart=/usr/bin/docker run --rm \--name temporal-worker \--env-file {{ worker_dir }}/.env \{{ artifact_registry }}/temporal-worker:{{ temporal_worker_image_tag }}This ensures that any fresh provisioning or reconfiguration automatically uses the appropriate environment-specific dynamic tag.
4. Deploy Pipeline Verification
Section titled “4. Deploy Pipeline Verification”The GCE deploy pipeline defined in cloudbuild-workers.yaml (under the deploy-temporal-workers task) runs:
sudo docker pull ${_REGISTRY}/temporal-worker:${COMMIT_SHA} &&sudo docker tag ${_REGISTRY}/temporal-worker:${COMMIT_SHA} ${_REGISTRY}/temporal-worker:latest-${BRANCH_NAME} &&sudo systemctl restart temporal-workerBecause the systemd unit references latest-${BRANCH_NAME}:
- Cloud Build pulls the precise built image (
COMMIT_SHA). - Cloud Build tags that image on the VM as
latest-staging(orlatest-main). - Running
systemctl restartstops the old container, and starts a new one which pulls/resolveslatest-${BRANCH_NAME}locally. - Since the local tag has been updated to point to the new
COMMIT_SHAimage, Docker runs the newly deployed code. docker image prune -afis then executed, which cleans up the untaggedCOMMIT_SHAimages while preserving the tagged ones and keeping disk usage bounded.
Conclusion & Recommendations
Section titled “Conclusion & Recommendations”The hypothesis that the systemd unit files pin a static image SHA is unverified and incorrect. The VMs and deployment pipeline are already configured to use dynamic, branch-specific tags, which are updated locally during each deployment cycle.
The only change this PR introduces is a VM-routing update in cloudbuild-workers.yaml: the deploy-temporal-workers step now routes staging builds to erp-staging-temporal-workers and main builds to erp-prod-temporal-workers, replacing the prior _TEMPORAL_WORKERS_VM substitution and the temporary skip-on-main guard. Aside from that pipeline routing change, the target behavior was already implemented correctly and no further code or pipeline modifications are needed.