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 Service | OJS Use | Equivalent to |
|---|---|---|
| Compute Engine | OJS application server | VPS / EC2 instance |
| Cloud SQL | Managed MySQL or PostgreSQL | Self-managed MySQL/PostgreSQL |
| Cloud Storage | OJS files_dir object storage | S3-compatible object store |
| Cloud CDN | Accelerate static/galley delivery | Cloudflare CDN |
| Cloud Load Balancing | Distribute traffic across app servers | Nginx upstream / HAProxy |
| Google Kubernetes Engine | Container orchestration | Docker Swarm / Kubernetes |
| Cloud Run | Serverless OJS container | Serverless container platform |
| Cloud Armor | WAF + DDoS protection | Cloudflare WAF |
| Secret Manager | Store config.inc.php credentials | Vault / environment secrets |
| Cloud Build | CI/CD for OJS upgrades | GitHub 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 Type | vCPU | RAM | Approx. Cost/mo | Use Case |
|---|---|---|---|---|
e2-small | 2 (shared) | 2 GB | ~$13 | Very small journals (dev/test) |
e2-medium | 2 (shared) | 4 GB | ~$27 | Small journals (< 200 submissions/yr) |
n2-standard-2 | 2 | 8 GB | ~$60 | Medium journals |
n2-standard-4 | 4 | 16 GB | ~$120 | Large / multi-journal |
n2-highcpu-4 | 4 | 4 GB | ~$85 | CPU-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:
| Tier | vCPU | RAM | Approx. Cost/mo | Use Case |
|---|---|---|---|---|
db-f1-micro | Shared | 0.6 GB | ~$7 | Dev/test only |
db-g1-small | Shared | 1.7 GB | ~$25 | Very small journals |
db-n1-standard-1 | 1 | 3.75 GB | ~$46 | Small-medium journals |
db-n1-standard-2 | 2 | 7.5 GB | ~$92 | Medium-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
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:
- In Google Cloud Console โ Cloud Storage โ Settings โ enable S3 interoperability
- Create HMAC keys (Access Key + Secret)
- 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
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:
- Snapshot the production VM disk
- Create a staging VM from the snapshot:
gcloud compute disks create ojs-staging-disk \--source-snapshot=ojs-pre-upgrade-20260502 \--zone=us-central1-agcloud 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
- 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")
- Test the upgrade on staging
- Apply to production after verification
- Delete staging resources to avoid charges
8. Cost Optimisationโ
| Strategy | Saving |
|---|---|
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 only | Up to 80% off staging VM cost |
| Use Cloud Storage + Cloud CDN for galleys/PDFs | Reduces VM bandwidth cost |
Use db-g1-small Cloud SQL for small journals | ~$25/mo instead of ~$46/mo |
| Archive old snapshots after 30 days | Reduces snapshot storage cost |
| Use Budget Alerts | Avoid 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โ
| Option | Complexity | Cost/mo (est.) | Best For |
|---|---|---|---|
| Compute Engine VM (self-managed DB) | Low | $27โ$60 | Most journals; similar to VPS |
| Compute Engine + Cloud SQL | Medium | $54โ$150 | Journals needing managed DB backups/HA |
| Compute Engine + Cloud Storage (files) | Medium | $27โ$70 | Multi-server or file storage redundancy |
| Compute Engine + Cloud CDN | Medium | $45โ$90 | High-traffic journals with global readers |
| GKE + Cloud SQL + Cloud Storage | High | $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โ
-
Google Cloud Compute Engine Documentation
https://cloud.google.com/compute/docs -
Google Cloud SQL Documentation
https://cloud.google.com/sql/docs -
Cloud SQL Auth Proxy
https://cloud.google.com/sql/docs/mysql/sql-proxy -
Google Cloud Storage Documentation
https://cloud.google.com/storage/docs -
Cloud CDN Documentation
https://cloud.google.com/cdn/docs -
Google Kubernetes Engine Documentation
https://cloud.google.com/kubernetes-engine/docs -
Cloud Armor WAF Documentation
https://cloud.google.com/armor/docs -
Secret Manager Documentation
https://cloud.google.com/secret-manager/docs -
pkp/docker-ojs (GitHub) โ Official OJS Docker image used in GKE deployments.
https://github.com/pkp/docker-ojs -
PKP Admin Guide
https://docs.pkp.sfu.ca/admin-guide/en/ -
PKP Community Forum โ Cloud Hosting Discussions
https://forum.pkp.sfu.ca/ -
Google Cloud Pricing Calculator
https://cloud.google.com/products/calculator