Skip to main content

CLI Deployment Guide

Builds a Docker image locally, pushes it to Artifact Registry, then updates the Cloud Run service. Use this for production deployments — it ensures the image in the registry matches exactly what runs in production.

1. Build and Tag the Image​

We build the image locally and push it to the Google Artifact Registry. Cloud Run cannot pull images from your laptop; it pulls from this registry.

1.1. Build the image Navigate to the root of the project and build the Docker image.

# Example tag: livekit-pipeline-local
sudo docker build -f pkg/google-cloud-uploader/Dockerfile -t livekit-pipeline-local .

1.2. Verify the build (Optional) You can run the container locally to ensure it starts without crashing.

sudo docker run --rm -p 8080:8080 \
-e PORT=8080 \
-e LIVEKIT_API_KEY=test \
-e LIVEKIT_API_SECRET=test \
livekit-pipeline-local node dist/service.js

2. Push to Artifact Registry​

2.1. Prepare GCP CLI Ensure you are logged in and targeting the correct project.

gcloud auth login
gcloud config set project video-uploader-480122

2.2. Create the Repository (First Time Only) If the repository containers does not exist in Artifact Registry:

gcloud artifacts repositories create containers \
--repository-format=docker \
--location=us-central1 \
--description="Video Pipeline Repository"

2.3. Tag the image We must tag the local image with the full remote path format: LOCATION-docker.pkg.dev/PROJECT_ID/REPO/IMAGE:TAG.

# Replace :v16 with your new version number
docker tag livekit-pipeline-local us-central1-docker.pkg.dev/video-uploader-480122/containers/qubital-pipeline:v16

2.4. Authenticate Docker with GCP This command pipes a temporary Google access token into the Docker login command. This avoids storing permanent credentials.

gcloud auth print-access-token | sudo docker login -u oauth2accesstoken --password-stdin https://us-central1-docker.pkg.dev

2.5. Push the image

docker push us-central1-docker.pkg.dev/video-uploader-480122/containers/qubital-pipeline:v15

3. Service Account Setup​

We follow the Least Privilege Principle. We create a specific Service Account (SA) that the Service uses to identify itself and trigger the Job.

3.1. Create the Service Account (First Time Only)

gcloud iam service-accounts create video-sa --display-name="Video Pipeline Service Account"

3.2. Export SA Email Save this variable to use in the deployment commands below.

export SA_EMAIL="video-sa@video-uploader-480122.iam.gserviceaccount.com"

4. Deploy the Cloud Run Job​

The Job is the "Worker" that performs the heavy lifting. It executes dist/job.ts.

Secrets Management

In the command below, ensure you replace the values with the actual production secrets. Do not commit real API keys to version control.

gcloud run jobs deploy video-upload-job \
--image us-central1-docker.pkg.dev/video-uploader-480122/containers/qubital-pipeline:v15 \
--region us-central1 \
--service-account $SA_EMAIL \
--command node \
--args dist/job.js \
--tasks 1 \
--max-retries 0 \
--task-timeout 60m \
--memory 2Gi \
--set-env-vars LIVEKIT_URL="wss://qubital-lk-4raif9fl.livekit.cloud/",\
LIVEKIT_API_KEY="...",\
LIVEKIT_API_SECRET="...",\
GOOGLE_CLIENT_ID="...",\
GOOGLE_CLIENT_SECRET="...",\
GOOGLE_REFRESH_TOKEN="...",\
GOOGLE_SHEET_ID="...",\
R2_BUCKET="qubital-storage",\
R2_ENDPOINT="https://b0c7740ea3c51309f74e94cbe625b502.r2.cloudflarestorage.com",\
R2_ACCESS_KEY="...",\
R2_SECRET_KEY="...",\
SLACK_BOT_TOKEN="...",\
SLACK_CHANNEL_ID="#video-call-archive"

5. Deploy the Cloud Run Service​

The Service is the "Listener" that receives WebHooks. It executes dist/service.js.

gcloud run deploy livekit-listener \
--image us-central1-docker.pkg.dev/video-uploader-480122/containers/qubital-pipeline:v15 \
--region us-central1 \
--service-account $SA_EMAIL \
--command node \
--args dist/service.js \
--allow-unauthenticated \
--set-env-vars JOB_NAME="video-upload-job",\
GOOGLE_CLOUD_PROJECT=video-uploader-480122,\
GOOGLE_CLOUD_REGION="us-central1",\
LIVEKIT_API_KEY="...",\
LIVEKIT_API_SECRET="..."

6. Connect Permissions​

This is the most critical step. By default, the Service cannot trigger the Job. We must explicitly grant the video-sa Service Account permission to invoke the Job.

# Grant "Invoker" permission
gcloud run jobs add-iam-policy-binding video-upload-job \
--region us-central1 \
--member "serviceAccount:$SA_EMAIL" \
--role "roles/run.invoker"

# Grant "Developer" permission (Required for override execution)
gcloud run jobs add-iam-policy-binding video-upload-job \
--region us-central1 \
--member "serviceAccount:$SA_EMAIL" \
--role "roles/run.developer"

7. Updating the Application​

When you have new code (e.g., v17), you do not need to re-type all the secrets. You can update just the image reference.

Step 1: Push new image

docker build ... -t ...:v16
docker push ...:v16

Step 2: Update Service

gcloud run deploy livekit-listener \
--image us-central1-docker.pkg.dev/video-uploader-480122/containers/qubital-pipeline:v16 \
--region us-central1

Step 3: Update Job

Note: We use jobs update here, not jobs deploy

gcloud run jobs update video-upload-job \
--image us-central1-docker.pkg.dev/video-uploader-480122/containers/qubital-pipeline:v16 \
--region us-central1