Ghost on Google Cloud Run: Getting /?errorCode=INVALID_TOKEN&success=false on Login/Invites (SQLite + GCS Sync)

Hi everyone,

I am running Ghost CMS inside a Google Cloud Run containerized environment. Because Cloud Run storage is ephemeral, I use a custom bash startup script that pulls down our production database (ghost.db) from a Google Cloud Storage (GCS) bucket via gsutil upon boot, and handles periodic sqlite3 .backup snapshots back to the bucket every few minutes.

The container is configured to listen on port 8080 (with a startup probe health check delay of 60 seconds).

The Problem

Everything works beautifully for general reading and navigation, but we are consistently hitting a wall with stateful actions. Whenever a user tries to log in via a Magic Link or accept a Staff Invitation, the browser gets redirected to:
/?errorCode=INVALID_TOKEN&success=false

What we suspect is happening

Because Cloud Run dynamically scales containers up and down, we believe a “split-brain” scenario or token mismatch is occurring between request cycles:

  1. Instance A spins up, processes the login request, writes the new cryptographic token to its local SQLite database instance, and triggers the email.
  2. Before the 5-minute backup loop syncs the changes back to GCS, the user clicks the email link.
  3. The routing engine hits a newly scaled Instance B (or Instance A has recycled). Instance B pulls the older database snapshot from the GCS bucket—which does not yet contain the token—resulting in an immediate token validation failure.

Our Environment

  • Ghost Version: Latest Docker Image
  • Database: SQLite (Syncing to GCS via gsutil)
  • Deployment: Google Cloud Run (Fully Managed)
  • Storage: Ephemeral container storage + GCS for media assets

Has anyone successfully run Ghost with SQLite on Cloud Run or a similar serverless engine without hitting this specific token race condition?

Short of migrating to a fully managed Google Cloud SQL (MySQL) instance—which is quite expensive for a smaller blog—are there recommended ways to optimize the scaling configurations, health checks, or script sync frequencies to ensure session tokens aren’t dropped between requests?

Any advice or architectural tweaks would be massively appreciated!

As of now, Ghost doesn’t support running across multiple instances.

Yeah, that’s one of the limitations I’ve noticed with Ghost too. I’m exploring a setup that can work around it,

[solved]

  • manage to move ghost.db from sqlite to mysql8 via avien free tier.
  • images still uses GCS using gsutil syncing every nsec
  • maintaining lowcost for a selfhosted ghost cloud run setup