Skip to main content

Running OJS on Google Cloud

Google Cloud Platform (GCP) offers a full range of infrastructure services that can host Open Journal Systems โ€” from a simple Compute Engine VM (equivalent to a VPS) to fully managed databases, object storage, Kubernetes clusters, and serverless containers. This article covers the most common GCP deployment patterns for OJS, from the simplest (single VM) to advanced (GKE + Cloud SQL + Cloud Storage).


1. GCP Services Relevant to OJSโ€‹

GCP ServiceOJS UseEquivalent to
Compute EngineOJS application serverVPS / EC2 instance
Cloud SQLManaged MySQL or PostgreSQLSelf-managed MySQL/PostgreSQL
Cloud StorageOJS files_dir object storageS3-compatible object store
Cloud CDNAccelerate static/galley deliveryCloudflare CDN
Cloud Load BalancingDistribute traffic across app serversNginx upstream / HAProxy
Google Kubernetes EngineContainer orchestrationDocker Swarm / Kubernetes
Cloud RunServerless OJS containerServerless container platform
Cloud ArmorWAF + DDoS protectionCloudflare WAF
Secret ManagerStore config.inc.php credentialsVault / environment secrets
Cloud BuildCI/CD for OJS upgradesGitHub Actions CI/CD

2. Deployment Option A: Compute Engine VM (Simplest)โ€‹

This is the most straightforward approach: a standard Linux VM that behaves exactly like a VPS from DigitalOcean or Linode.

2.1 Create a VM Instanceโ€‹

# Using gcloud CLI (install from https://cloud.google.com/sdk/)
gcloud compute instances create ojs-server \
--zone=us-central1-a \
--machine-type=e2-medium \ # 2 vCPU / 4 GB RAM (~$27/mo)
--image-family=ubuntu-2204-lts \
--image-project=ubuntu-os-cloud \
--boot-disk-size=50GB \
--boot-disk-type=pd-ssd \
--tags=http-server,https-server

GCP VM types for OJS:

Machine TypevCPURAMApprox. Cost/moUse Case
e2-small2 (shared)2 GB~$13Very small journals (dev/test)
e2-medium2 (shared)4 GB~$27Small journals (< 200 submissions/yr)
n2-standard-228 GB~$60Medium journals
n2-standard-4416 GB~$120Large / multi-journal
n2-highcpu-444 GB~$85CPU-intensive (PDF generation)

GCP Pricing: https://cloud.google.com/compute/vm-instance-pricing

2.2 Configure Firewall Rulesโ€‹

# Allow HTTP and HTTPS traffic
gcloud compute firewall-rules create allow-http \
--action=ALLOW \
--rules=tcp:80 \
--target-tags=http-server

gcloud compute firewall-rules create allow-https \
--action=ALLOW \
--rules=tcp:443 \
--target-tags=https-server

2.3 Assign a Static External IPโ€‹

gcloud compute addresses create ojs-static-ip \
--region=us-central1

gcloud compute instances add-access-config ojs-server \
--access-config-name="External NAT" \
--address=$(gcloud compute addresses describe ojs-static-ip \
--region=us-central1 --format='get(address)')

2.4 Install OJS on the VMโ€‹

SSH into the VM and install the LEMP stack (see the VPS & Control Panels article for detailed Nginx + PHP-FPM setup). The process is identical to any Ubuntu 22.04 VPS:

gcloud compute ssh ojs-server --zone=us-central1-a

# On the VM:
apt update && apt install -y nginx mariadb-server \
php8.2-fpm php8.2-mysql php8.2-xml php8.2-mbstring \
php8.2-gd php8.2-zip php8.2-curl php8.2-intl certbot \
python3-certbot-nginx

2.5 Configure a GCP Startup Script (Optional Automation)โ€‹

Attach a startup script to automate the initial server setup:

gcloud compute instances create ojs-server \
--metadata-from-file startup-script=/tmp/ojs-setup.sh \
...

3. Deployment Option B: Compute Engine + Cloud SQLโ€‹

Cloud SQL is GCP's fully managed MySQL/PostgreSQL service. Using Cloud SQL instead of a local MariaDB instance gives you:

  • Automated daily backups with point-in-time recovery
  • Automatic failover (HA configuration)
  • Read replicas for scaling
  • Managed security patches and upgrades
  • No need to manage a database VM separately

3.1 Create a Cloud SQL Instance (MySQL)โ€‹

gcloud sql instances create ojs-db \
--database-version=MYSQL_8_0 \
--tier=db-n1-standard-1 \ # 1 vCPU / 3.75 GB RAM
--region=us-central1 \
--storage-type=SSD \
--storage-size=20GB \
--backup-start-time=02:00 \
--enable-bin-log # Required for point-in-time recovery

# Create the OJS database
gcloud sql databases create ojs_production --instance=ojs-db

# Create a user
gcloud sql users create ojsuser \
--instance=ojs-db \
--password=STRONG_PASSWORD_HERE

Cloud SQL tiers for OJS:

TiervCPURAMApprox. Cost/moUse Case
db-f1-microShared0.6 GB~$7Dev/test only
db-g1-smallShared1.7 GB~$25Very small journals
db-n1-standard-113.75 GB~$46Small-medium journals
db-n1-standard-227.5 GB~$92Medium-large journals

3.2 Connect the VM to Cloud SQLโ€‹

Use the Cloud SQL Auth Proxy โ€” the recommended way to connect from Compute Engine to Cloud SQL without exposing the database to the public internet.

# On the VM: download and start the Cloud SQL Auth Proxy
wget https://storage.googleapis.com/cloud-sql-connectors/cloud-sql-proxy/v2.1.0/cloud-sql-proxy.linux.amd64 \
-O /usr/local/bin/cloud-sql-proxy
chmod +x /usr/local/bin/cloud-sql-proxy

# Run the proxy (replace PROJECT:REGION:INSTANCE with your values)
/usr/local/bin/cloud-sql-proxy \
--port=3306 \
YOUR_PROJECT:us-central1:ojs-db &

Then in config.inc.php:

driver = mysqli
host = 127.0.0.1
port = 3306
username = ojsuser
password = STRONG_PASSWORD_HERE
name = ojs_production

Source: Cloud SQL Auth Proxy Documentation

3.3 Cloud SQL for PostgreSQLโ€‹

OJS also works with Cloud SQL for PostgreSQL:

gcloud sql instances create ojs-pg-db \
--database-version=POSTGRES_15 \
--tier=db-n1-standard-1 \
--region=us-central1

gcloud sql databases create ojs_production --instance=ojs-pg-db
gcloud sql users create ojsuser --instance=ojs-pg-db --password=STRONG_PASSWORD_HERE

Set driver = postgres9 in config.inc.php.


4. Deployment Option C: OJS Files on Cloud Storageโ€‹

OJS uses a files_dir directory to store submission files, galley PDFs, and user uploads. On a single VM this is a local directory. For multi-server or managed deployments, Google Cloud Storage provides a scalable, durable alternative.

4.1 The PKP S3-Compatible Object Storage Pluginโ€‹

OJS 3.4+ includes the S3-compatible object storage plugin which works with any S3-compatible API, including Google Cloud Storage's XML API.

Enable Cloud Storage S3 interoperability:

  1. In Google Cloud Console โ†’ Cloud Storage โ†’ Settings โ†’ enable S3 interoperability
  2. Create HMAC keys (Access Key + Secret)
  3. Create a Cloud Storage bucket for OJS files

Configure the OJS plugin:

In Settings โ†’ Website โ†’ Plugins โ†’ Installed Plugins โ†’ S3 Files (or Generic Object Storage plugin):

  • Endpoint: https://storage.googleapis.com
  • Bucket: your-ojs-files-bucket
  • Access Key: (HMAC access key)
  • Secret Key: (HMAC secret)
  • Region: us-central1 (or your bucket's region)

4.2 Alternative: GCS FUSE Mountโ€‹

If you don't want to use the OJS plugin, mount a Cloud Storage bucket as a local filesystem using gcsfuse:

# Install gcsfuse
export GCSFUSE_REPO=gcsfuse-$(lsb_release -c -s)
echo "deb https://packages.cloud.google.com/apt $GCSFUSE_REPO main" \
> /etc/apt/sources.list.d/gcsfuse.list
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
apt update && apt install -y gcsfuse

# Mount the bucket as the OJS files directory
mkdir -p /var/www/ojs-files
gcsfuse --file-mode=0755 --dir-mode=0755 \
--uid=$(id -u www-data) --gid=$(id -g www-data) \
your-ojs-files-bucket /var/www/ojs-files

Add to /etc/fstab for persistent mount:

your-ojs-files-bucket /var/www/ojs-files gcsfuse rw,_netdev,allow_other,file_mode=0755,dir_mode=0755 0 0

Caution: GCS FUSE adds network latency to every file operation. For high-submission-volume journals, the object storage plugin approach is more efficient.


5. Deployment Option D: Cloud CDN for OJSโ€‹

Google Cloud CDN caches responses at Google's global edge network (Points of Presence). For OJS, Cloud CDN is most useful for caching:

  • Published article pages (HTML)
  • Static assets (CSS, JS, images)
  • Galley PDFs (via Cloud Storage)

5.1 Architecture with Cloud CDNโ€‹

Users โ†’ Cloud CDN Edge โ†’ (Cache miss) โ†’ Cloud Load Balancer โ†’ OJS VM(s)
โ†—
Cloud Storage (PDFs/files)

5.2 Enable Cloud CDN via Load Balancerโ€‹

Cloud CDN is configured at the backend service level on a Google Cloud Load Balancer (HTTPS):

# Create a backend service with Cloud CDN enabled
gcloud compute backend-services create ojs-backend \
--protocol=HTTPS \
--port-name=https \
--global \
--enable-cdn \
--cache-mode=CACHE_ALL_STATIC

Note: Cloud CDN requires a Cloud Load Balancer, which has a base cost of ~$18/mo. This is only cost-effective for journals with substantial traffic. For small/medium journals, use Cloudflare's free CDN instead.

Source: Google Cloud CDN Documentation


6. Deployment Option E: Google Kubernetes Engine (GKE)โ€‹

For advanced DevOps teams, OJS can be deployed on Google Kubernetes Engine using the official pkp/docker-ojs Docker image.

6.1 Minimal GKE OJS Deploymentโ€‹

# ojs-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: ojs
spec:
replicas: 2
selector:
matchLabels:
app: ojs
template:
metadata:
labels:
app: ojs
spec:
containers:
- name: ojs
image: pkpinc/ojs:3.5.0
ports:
- containerPort: 80
env:
- name: OJS_DB_HOST
value: "127.0.0.1" # Cloud SQL Auth Proxy sidecar
- name: OJS_DB_USER
valueFrom:
secretKeyRef:
name: ojs-db-secret
key: username
- name: OJS_DB_PASSWORD
valueFrom:
secretKeyRef:
name: ojs-db-secret
key: password
- name: OJS_DB_NAME
value: "ojs_production"
volumeMounts:
- name: ojs-files
mountPath: /var/www/files
# Cloud SQL Auth Proxy sidecar
- name: cloud-sql-proxy
image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:2
args:
- "--structured-logs"
- "--port=3306"
- "YOUR_PROJECT:us-central1:ojs-db"
volumes:
- name: ojs-files
persistentVolumeClaim:
claimName: ojs-files-pvc
---
apiVersion: v1
kind: Service
metadata:
name: ojs-service
spec:
selector:
app: ojs
ports:
- port: 80
targetPort: 80
type: ClusterIP

Create GKE cluster:

gcloud container clusters create ojs-cluster \
--num-nodes=2 \
--machine-type=n2-standard-2 \
--zone=us-central1-a \
--enable-autoscaling \
--min-nodes=1 \
--max-nodes=4

Use Cloud Storage as Persistent Volume (via CSI driver):

# Enable Cloud Storage FUSE CSI driver
gcloud container clusters update ojs-cluster \
--update-addons GcsFuseCsiDriver=ENABLED \
--zone=us-central1-a

Source: pkp/docker-ojs GitHub Repository, GKE Documentation


7. OJS Upgrades on Google Cloudโ€‹

7.1 Snapshot Before Upgrade (Compute Engine)โ€‹

Always take a VM snapshot before an OJS upgrade:

# Create a disk snapshot
gcloud compute disks snapshot ojs-server \
--snapshot-names=ojs-pre-upgrade-$(date +%Y%m%d) \
--zone=us-central1-a

To roll back, create a new disk from the snapshot and re-attach it.

7.2 Cloud SQL Backup Before Upgradeโ€‹

# Create an on-demand Cloud SQL backup
gcloud sql backups create \
--instance=ojs-db \
--description="Pre-upgrade backup $(date +%Y-%m-%d)"

7.3 Staging with GCP Snapshotsโ€‹

The GCP-native staging workflow for OJS:

  1. Snapshot the production VM disk
  2. Create a staging VM from the snapshot:
    gcloud compute disks create ojs-staging-disk \
    --source-snapshot=ojs-pre-upgrade-20260502 \
    --zone=us-central1-a

    gcloud compute instances create ojs-staging \
    --disk=name=ojs-staging-disk,boot=yes,auto-delete=yes \
    --zone=us-central1-a \
    --machine-type=e2-medium \
    --no-address # Internal only โ€” no external IP
  3. Clone the Cloud SQL database to a staging instance:
    gcloud sql instances clone ojs-db ojs-staging-db \
    --point-in-time=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")
  4. Test the upgrade on staging
  5. Apply to production after verification
  6. Delete staging resources to avoid charges

8. Cost Optimisationโ€‹

StrategySaving
Use e2-medium instead of n1-standard-2~50% VM cost reduction for equivalent performance
Use Committed Use Discounts (1-year or 3-year)Up to 57% off VM cost
Enable Preemptible/Spot VMs for staging onlyUp to 80% off staging VM cost
Use Cloud Storage + Cloud CDN for galleys/PDFsReduces VM bandwidth cost
Use db-g1-small Cloud SQL for small journals~$25/mo instead of ~$46/mo
Archive old snapshots after 30 daysReduces snapshot storage cost
Use Budget AlertsAvoid surprise costs
# Set up a budget alert at $100/mo
gcloud billing budgets create \
--billing-account=BILLING_ACCOUNT_ID \
--display-name="OJS Monthly Budget" \
--budget-amount=100USD \
--threshold-rule=percent=80 \
--threshold-rule=percent=100

9. Security on Google Cloudโ€‹

9.1 IAM Roles for OJS Operationsโ€‹

Use least-privilege IAM roles:

# Create a service account for the OJS VM
gcloud iam service-accounts create ojs-vm-sa \
--display-name="OJS VM Service Account"

# Grant only the required roles
gcloud projects add-iam-policy-binding YOUR_PROJECT \
--member="serviceAccount:ojs-vm-sa@YOUR_PROJECT.iam.gserviceaccount.com" \
--role="roles/cloudsql.client"

gcloud projects add-iam-policy-binding YOUR_PROJECT \
--member="serviceAccount:ojs-vm-sa@YOUR_PROJECT.iam.gserviceaccount.com" \
--role="roles/storage.objectAdmin"

Attach the service account to the VM:

gcloud compute instances set-service-account ojs-server \
--service-account=ojs-vm-sa@YOUR_PROJECT.iam.gserviceaccount.com \
--scopes=cloud-platform \
--zone=us-central1-a

9.2 Cloud Armor for WAF Protectionโ€‹

Google Cloud Armor provides Web Application Firewall (WAF) rules:

# Create a Cloud Armor security policy
gcloud compute security-policies create ojs-security-policy \
--description="OJS WAF policy"

# Block common web attacks (SQLi, XSS)
gcloud compute security-policies rules create 1000 \
--security-policy=ojs-security-policy \
--expression="evaluatePreconfiguredExpr('sqli-v33-stable')" \
--action=deny-403

# Rate limit to prevent brute force on /login
gcloud compute security-policies rules create 2000 \
--security-policy=ojs-security-policy \
--expression="request.path.matches('/index.php/*/login')" \
--action=throttle \
--rate-limit-threshold-count=20 \
--rate-limit-threshold-interval-sec=60 \
--conform-action=allow \
--exceed-action=deny-429 \
--enforce-on-key=IP

9.3 Store OJS Database Password in Secret Managerโ€‹

# Store the database password
echo -n "STRONG_PASSWORD_HERE" | \
gcloud secrets create ojs-db-password --data-file=-

# Grant the VM service account read access
gcloud secrets add-iam-policy-binding ojs-db-password \
--member="serviceAccount:ojs-vm-sa@YOUR_PROJECT.iam.gserviceaccount.com" \
--role="roles/secretmanager.secretAccessor"

# Retrieve in a startup script
DB_PASS=$(gcloud secrets versions access latest --secret=ojs-db-password)

10. Monitoring OJS on Google Cloudโ€‹

10.1 Cloud Monitoring (formerly Stackdriver)โ€‹

Install the Ops Agent to collect VM metrics and logs:

curl -sSO https://dl.google.com/cloudagents/add-google-cloud-ops-agent-repo.sh
sudo bash add-google-cloud-ops-agent-repo.sh --also-install

This streams:

  • CPU, memory, disk, network metrics to Cloud Monitoring
  • Nginx/PHP access and error logs to Cloud Logging

10.2 Alerting on OJS Errorsโ€‹

Create an uptime check:

gcloud monitoring uptime create \
--display-name="OJS Uptime Check" \
--uri="https://submit.journal.org/index.php/index" \
--check-interval=60 \
--timeout=10

11. Deployment Comparison Summaryโ€‹

OptionComplexityCost/mo (est.)Best For
Compute Engine VM (self-managed DB)Low$27โ€“$60Most journals; similar to VPS
Compute Engine + Cloud SQLMedium$54โ€“$150Journals needing managed DB backups/HA
Compute Engine + Cloud Storage (files)Medium$27โ€“$70Multi-server or file storage redundancy
Compute Engine + Cloud CDNMedium$45โ€“$90High-traffic journals with global readers
GKE + Cloud SQL + Cloud StorageHigh$150โ€“$300+DevOps-led; multi-journal; CI/CD upgrades

12. Quick-Start: OJS on GCP in 30 Minutesโ€‹

# 1. Create VM
gcloud compute instances create ojs-prod \
--zone=us-central1-a --machine-type=e2-medium \
--image-family=ubuntu-2204-lts --image-project=ubuntu-os-cloud \
--boot-disk-size=50GB --boot-disk-type=pd-ssd \
--tags=http-server,https-server

# 2. Open firewall
gcloud compute firewall-rules create allow-web \
--action=ALLOW --rules=tcp:80,tcp:443 --target-tags=http-server,https-server

# 3. Reserve and assign static IP
gcloud compute addresses create ojs-ip --region=us-central1

# 4. SSH and install stack
gcloud compute ssh ojs-prod --zone=us-central1-a --command="
sudo apt update -y && sudo apt install -y nginx mariadb-server \
php8.2-fpm php8.2-mysql php8.2-xml php8.2-mbstring php8.2-gd \
php8.2-zip php8.2-curl php8.2-intl certbot python3-certbot-nginx
"

# 5. Download OJS (get latest version URL from https://pkp.sfu.ca/software/ojs/download/)
gcloud compute ssh ojs-prod --zone=us-central1-a --command="
wget https://pkp.sfu.ca/ojs/download/ojs-3.5.0.tar.gz -O /tmp/ojs.tar.gz
sudo tar -xzf /tmp/ojs.tar.gz -C /var/www/
sudo mv /var/www/ojs-* /var/www/ojs
sudo chown -R www-data:www-data /var/www/ojs
"

# 6. Configure and install via browser at http://YOUR_STATIC_IP

Referencesโ€‹

  1. Google Cloud Compute Engine Documentation
    https://cloud.google.com/compute/docs

  2. Google Cloud SQL Documentation
    https://cloud.google.com/sql/docs

  3. Cloud SQL Auth Proxy
    https://cloud.google.com/sql/docs/mysql/sql-proxy

  4. Google Cloud Storage Documentation
    https://cloud.google.com/storage/docs

  5. Cloud CDN Documentation
    https://cloud.google.com/cdn/docs

  6. Google Kubernetes Engine Documentation
    https://cloud.google.com/kubernetes-engine/docs

  7. Cloud Armor WAF Documentation
    https://cloud.google.com/armor/docs

  8. Secret Manager Documentation
    https://cloud.google.com/secret-manager/docs

  9. pkp/docker-ojs (GitHub) โ€” Official OJS Docker image used in GKE deployments.
    https://github.com/pkp/docker-ojs

  10. PKP Admin Guide
    https://docs.pkp.sfu.ca/admin-guide/en/

  11. PKP Community Forum โ€” Cloud Hosting Discussions
    https://forum.pkp.sfu.ca/

  12. Google Cloud Pricing Calculator
    https://cloud.google.com/products/calculator