Step-by-step guide for provisioning a fresh Ubuntu 22.04 server to run paperscout alongside other apps that share the same PostgreSQL and nginx.
sudo apt update && sudo apt upgrade -y
# Harden SSH: disable password auth
sudo sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo systemctl restart ssh# Install prerequisites
sudo apt install -y ca-certificates curl gnupg
# Add Docker's official GPG key
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# Add the repository
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Install
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
# Let the deploy user run docker without sudo
sudo usermod -aG docker gcp-cppdigest
newgrp dockerIf PostgreSQL is already running for other apps, skip the install and jump to Create the paperscout database.
# Add PGDG repo
sudo apt install -y wget
sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" \
> /etc/apt/sources.list.d/pgdg.list'
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | \
sudo apt-key add -
sudo apt update
sudo apt install -y postgresql-16
# Start and enable
sudo systemctl enable --now postgresqlsudo -u postgres psql <<'SQL'
CREATE USER paperscout WITH PASSWORD <password>;
CREATE DATABASE paperscout OWNER paperscout;
SQLIf you are replacing an old server that already has a running paperscout database, dump it on the old server and restore it on the new one:
# --- On the OLD server ---
pg_dump -U postgres -Fc paperscout > /tmp/paperscout.dump
# or on Windows
"C:/Program Files/PostgreSQL/18/bin/pg_dump" -U postgres -Fc paperscout > paperscout.dump
# Copy the dump to the new server
scp /tmp/paperscout.dump <user>@<new-server>:/tmp/paperscout.dump# --- On the NEW server (after creating the database above) ---
pg_restore -U paperscout -d paperscout --no-owner /tmp/paperscout.dump
rm /tmp/paperscout.dumpIf the dump is stored in GCS (from the daily backup workflow), download it directly on the new server instead:
gsutil cp gs://paperscout-backups/paperscout-<YYYYMMDD>.dump /tmp/paperscout.dump
pg_restore -U paperscout -h localhost -d paperscout --no-owner /tmp/paperscout.dump
rm /tmp/paperscout.dumpDocker containers reach the host via host.docker.internal, which
resolves to the Docker gateway IP (typically 172.17.0.1). PostgreSQL
must accept connections from that subnet.
# postgresql.conf — listen on all interfaces (or at least 172.17.0.1)
sudo sed -i "s/^#listen_addresses.*/listen_addresses = '*'/" \
/etc/postgresql/16/main/postgresql.conf
# pg_hba.conf — allow the Docker bridge subnet
echo "host paperscout paperscout 172.16.0.0/12 scram-sha-256" | \
sudo tee -a /etc/postgresql/16/main/pg_hba.conf
sudo systemctl restart postgresqlNote:
172.16.0.0/12covers all default Docker networks (172.16.0.0–172.31.255.255). Tighten this if you know the exact bridge subnet (docker network inspect bridge).
sudo apt install -y nginx
# Obtain a Let's Encrypt certificate (skip if already done for this domain)
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d dev.cppdigest.orgCertbot creates a server block for dev.cppdigest.org in the default
nginx config. Add the paperscout location blocks inside that existing
server block (do NOT create a separate server block -- nginx will
ignore it in favour of the first match).
Open the config and find the dev.cppdigest.org server block with
listen 443 ssl;. Add these lines before its closing }:
# --- paperscout ---
location /paperscout/health {
proxy_pass http://127.0.0.1:9101/health;
}
location /paperscout/ {
proxy_pass http://127.0.0.1:9100/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}A reference copy of these blocks lives in
deploy/paperscout.conf.
sudo nginx -t && sudo systemctl reload nginxClone the repo into /opt/paperscout:
sudo mkdir -p /opt
sudo git clone https://github.com/cppalliance/paperscout-python.git /opt/paperscout
sudo chown -R gcp-cppdigest:gcp-cppdigest /opt/paperscoutCreate the .env file:
cd /opt/paperscout
cp .env.example .env
# Edit with real credentials:
# SLACK_SIGNING_SECRET, SLACK_BOT_TOKEN, DATABASE_URL, NOTIFICATION_CHANNEL
nano .envThe DATABASE_URL should use host.docker.internal:
DATABASE_URL=postgresql://paperscout:<password>@host.docker.internal:5432/paperscout
Note: If the password contains special characters, they must be percent-encoded in the URL (e.g.
@→%40,!→%21). Usepython3 -c "import urllib.parse; print(urllib.parse.quote('<password>', safe=''))"to encode it.
cd /opt/paperscout
docker compose up -d --build
# Verify
sleep 5
curl -sf http://localhost:9101/health | python3 -m json.tool
docker compose logs -f paperscoutIf migrating from another server with an existing database:
gsutil cp gs://paperscout-backups/paperscout-<YYYYMMDD>.dump /tmp/paperscout.dump
pg_restore -U paperscout -h localhost -d paperscout -c /tmp/paperscout.dump
rm /tmp/paperscout.dumpThe db-backup.yml GitHub Actions workflow SSHes into the server daily
and runs pg_dump + gsutil cp to upload to GCS. The VM's service
account handles authentication automatically — no credentials needed.
The GCS bucket paperscout-backups should have a lifecycle rule to
auto-delete objects older than 30 days (configured in the Cloud Console
under the bucket's Lifecycle tab).
Configure these in the repo under Settings → Secrets and variables → Actions:
| Secret | Purpose |
|---|---|
SERVER_HOST |
Server IP or hostname |
SERVER_USER |
SSH username (e.g. gcp-cppdigest) |
SERVER_SSH_KEY |
Private SSH key for the deploy user |
SERVER_PORT |
SSH port (optional, defaults to 22) |
GITHUB_TOKEN is provided automatically by GitHub Actions.
GCS authentication uses the VM's service account — no extra secrets needed.