Certification track: Associate Cloud Engineer (ACE)
Paperless-ngx on Google Cloud Run
This document provides a comprehensive reference for the modules/Paperless_CloudRun module. It covers architecture, IAM, configuration variables, Paperless-ngx-specific behaviours, and operational patterns for deploying Paperless-ngx on Google Cloud Run (v2).
1. Module Overview
Paperless-ngx is a community-supported open-source document management system that transforms paper documents into a searchable digital archive. Paperless CloudRun is a wrapper module built on top of App CloudRun. It uses App CloudRun for all GCP infrastructure provisioning and injects Paperless-ngx-specific application configuration, database initialisation, and storage configuration via Paperless Common.
Key Capabilities:
- Compute: Cloud Run v2 (Gen2), Python/gunicorn container, 2 vCPU / 2 Gi by default.
min_instance_count = 0by default (scale-to-zero) — set to1or more to keep the background consumption pipeline warm and avoid cold-start delays on document uploads. - OCR & Classification: Tesseract OCR engine with configurable language packs (
ocr_language). Machine learning document classification, full-text search, tags, correspondents, document types, and custom fields. - Data Persistence: Cloud SQL PostgreSQL 15. GCS FUSE volume auto-mounted at
/usr/src/paperless/mediafor persistent document storage (processed documents, thumbnails, originals). - Task Queue: Redis required (not optional). Paperless-ngx uses Redis as a Celery message broker for background OCR processing, document classification, consumption pipeline, and async tasks.
- Security: Inherits Cloud Armor WAF, IAP, Binary Authorization, and VPC Service Controls from
App CloudRun. Two secrets auto-generated byPaperless Common:PAPERLESS_ADMIN_PASSWORDandPAPERLESS_SECRET_KEY. - CI/CD: Cloud Build custom image pipeline by default; Cloud Deploy progressive delivery optional.
- Reliability: Health probes target
/(the Paperless-ngx login page) with a 60-second initial delay to accommodate database migrations and Celery worker startup on first boot.
Project & Application Identity
| Variable | Group | Type | Default | Description |
|---|---|---|---|---|
project_id | 1 | string | — | GCP project ID. Required. |
tenant_deployment_id | 2 | string | 'demo' | Short suffix appended to all resource names. |
support_users | 2 | list(string) | [] | Email recipients for monitoring alerts. |
resource_labels | 2 | map(string) | {} | Labels applied to all provisioned resources. |
application_name | 3 | string | 'paperless' | Base resource name. Do not change after initial deployment. |
display_name | 3 | string | 'Paperless-ngx - Document Management System' | Human-readable name shown in dashboards. |
description | 3 | string | 'Paperless-ngx - open-source document management system with OCR...' | Service description. |
application_version | 3 | string | 'latest' | Container image version tag. Pin to a specific version (e.g., '2.13.5') for production. |
Wrapper architecture: Paperless CloudRun calls Paperless Common to build an application_config object containing Paperless-ngx-specific environment variables, probe configuration, storage bucket definitions, and the db-init job definition. module_storage_buckets carries the paperless-media bucket provisioned by Paperless Common. scripts_dir is resolved to the Paperless Common scripts directory at apply time.
2. IAM & Access Control
Paperless_CloudRun delegates all IAM provisioning to App_CloudRun. The Cloud Run SA, Cloud Build SA, IAP service agent, and password rotation role sets are identical to those in App_CloudRun.
Application secrets: Unlike Ghost, Paperless Common auto-generates two application-level secrets at deploy time:
| Secret | Description |
|---|---|
PAPERLESS_ADMIN_PASSWORD | Password for the initial admin account. Auto-generated and stored in Secret Manager. |
PAPERLESS_SECRET_KEY | Django application secret key. Auto-generated and stored in Secret Manager. |
Both secrets are injected natively by Cloud Run at revision start — the plaintext is never written to Terraform state.
Database initialisation identity: The db-init Cloud Run Job runs under the Cloud Run SA. It connects to Cloud SQL PostgreSQL via the Auth Proxy Unix socket (since enable_cloudsql_volume = true by default).
120-second IAM propagation delay: Inherited from App CloudRun — the Paperless-ngx service is not deployed until the delay completes, preventing secret-read failures on the first revision start.
For the complete role tables and IAP, password rotation, and public access details, see the App_CloudRun documentation.
3. Core Service Configuration
A. Compute (Cloud Run)
Paperless-ngx is a Python Django/gunicorn application with background Celery workers. It performs OCR using Tesseract, which is CPU-intensive on document ingestion. Paperless CloudRun exposes cpu_limit and memory_limit as dedicated top-level variables with production-ready defaults.
min_instance_count = 0 by default. Like most modules in this repository, Paperless-ngx scales to zero by default (cost-first). The trade-off: the background consumption pipeline only runs while an instance is warm, so documents dropped into the consumption directory may sit unprocessed until the next request wakes the service. Set min_instance_count = 1 or more to keep the consumption pipeline continuously listening and avoid this delay.
Startup CPU Boost is always enabled (hardcoded in App CloudRun).
Container image: container_image_source defaults to 'custom', meaning Cloud Build compiles a custom image using Paperless_Common's Dockerfile (based on ghcr.io/paperless-ngx/paperless-ngx). Set container_image_source = 'prebuilt' and container_image = 'ghcr.io/paperless-ngx/paperless-ngx:2.13.5' to deploy an existing image directly.
Image mirroring enabled by default (enable_image_mirroring = true): Paperless-ngx images are hosted on GitHub Container Registry (GHCR). The module mirrors the image to Artifact Registry to avoid GHCR rate limits and to satisfy Binary Authorization requirements.
| Variable | Group | Default | Description |
|---|---|---|---|
deploy_application | 4 | true | Set false for infrastructure-only deployment (SQL, storage, secrets). |
container_image_source | — | 'custom' | 'custom' builds via Cloud Build. 'prebuilt' deploys an existing image URI. |
container_image | — | "" | Override image URI. Leave empty for Cloud Build to manage. |
cpu_limit | 4 | '2000m' | CPU per instance. 2 vCPU recommended for OCR workloads. |
memory_limit | 4 | '2Gi' | Memory per instance. 2 Gi minimum; increase for large document batches. |
min_instance_count | 4 | 0 | Minimum running instances. Scale-to-zero by default; set to 1+ to keep the consumption pipeline active. |
max_instance_count | 4 | 3 | Maximum running instances. Cost ceiling. |
container_port | 4 | 8000 | Paperless-ngx gunicorn port. Do not change without matching the Dockerfile. |
execution_environment | 4 | 'gen2' | Gen2 required for GCS Fuse volume mounts. |
timeout_seconds | 4 | 300 | Max request duration. OCR on large documents can be slow; 300s or higher recommended. |
enable_cloudsql_volume | 4 | true | Default true — connects via Cloud SQL Auth Proxy Unix socket. |
enable_image_mirroring | 4 | true | Mirrors the GHCR image into Artifact Registry. Recommended to avoid rate limits. |
container_protocol | 4 | 'http1' | 'http1' or 'h2c'. |
traffic_split | 4 | [] | Percentage-based canary/blue-green traffic allocation. |
max_revisions_to_retain | 4 | 7 | Maximum Cloud Run revisions to keep. Set 0 to disable. |
service_annotations | 4 | {} | Advanced Cloud Run annotations. |
service_labels | 4 | {} | Labels applied to the Cloud Run service. |
Differences from App CloudRun defaults:
| Variable | App CloudRun | Paperless CloudRun | Reason |
|---|---|---|---|
container_port | 8080 | 8000 | Paperless-ngx gunicorn listens on 8000. |
cpu_limit | '1000m' | '2000m' | OCR with Tesseract is CPU-intensive; 2 vCPU recommended. |
memory_limit | '512Mi' | '2Gi' | Paperless-ngx loads document thumbnails and ML models in memory. |
enable_image_mirroring | false | true | Mirrors from GHCR to avoid rate limits and support Binary Authorization. |
B. Database (Cloud SQL — PostgreSQL 15)
Paperless-ngx requires PostgreSQL. Paperless Common fixes database_type = "POSTGRES_15" and configures the connection variables accordingly. MySQL and SQLite are not supported.
Unix socket connection: enable_cloudsql_volume defaults to true. App CloudRun injects the Auth Proxy sidecar and sets DB_HOST to the socket path under /cloudsql.
| Variable | Group | Default | Description |
|---|---|---|---|
database_type | 12 | 'POSTGRES_15' | PostgreSQL 15. Do not change — Paperless-ngx requires PostgreSQL. |
db_name | 12 | 'paperless' | PostgreSQL database name. Do not change after initial deployment. |
db_user | 12 | 'paperless' | PostgreSQL application user. Password auto-generated and stored in Secret Manager. |
database_password_length | 12 | 32 | Auto-generated password length. Range: 16–64. |
enable_auto_password_rotation | 12 | false | Automated zero-downtime password rotation. |
rotation_propagation_delay_sec | 12 | 90 | Seconds to wait after rotation before restarting the service. |
C. Storage (GCS Fuse)
GCS Fuse is the primary persistent storage mechanism. Unlike Ghost (which relies on NFS for content), Paperless-ngx stores all processed documents, thumbnails, and originals in a GCS FUSE-mounted bucket at /usr/src/paperless/media. This design makes Paperless-ngx well-suited for Cloud Run — documents survive instance recycling and scale-to-zero because they are in GCS, not on local disk.
Default GCS Fuse volume: When gcs_volumes = [] (the default), Paperless Common auto-provisions a paperless-media GCS bucket and mounts it at /usr/src/paperless/media with implicit-dirs, stat-cache-ttl=60s, and type-cache-ttl=60s options. GCS Fuse requires execution_environment = 'gen2'.
NFS is also enabled by default (enable_nfs = true). The NFS server IP is used as the Redis host when no redis_host is explicitly configured. NFS also provides a local-filesystem fallback for the consumption directory when needed. Requires execution_environment = 'gen2'.
| Variable | Group | Default | Description |
|---|---|---|---|
gcs_volumes | 11 | [] | GCS buckets to mount via GCS Fuse. When empty, Paperless Common auto-mounts the paperless-media bucket at /usr/src/paperless/media. Requires gen2. |
create_cloud_storage | 11 | true | Set false to skip additional GCS bucket creation. |
storage_buckets | 11 | [] | Additional GCS buckets beyond the auto-provisioned media bucket. |
enable_nfs | 11 | true | Provisions NFS server. Used as the Redis host when redis_host is blank. Requires gen2. |
nfs_mount_path | 11 | '/mnt/nfs' | Container path for the NFS mount. |
nfs_instance_name | 11 | "" | Name of an existing NFS GCE VM. Leave empty to auto-discover or provision inline. |
nfs_instance_base_name | 11 | 'app-nfs' | Base name for an inline NFS GCE VM. Deployment ID is appended. |
manage_storage_kms_iam | 11 | false | Creates a CMEK KMS keyring and enables CMEK on all storage buckets. |
enable_artifact_registry_cmek | 11 | false | Creates an Artifact Registry KMS key for at-rest image encryption. |
D. Networking
| Variable | Group | Default | Description |
|---|---|---|---|
ingress_settings | 5 | 'all' | 'all' — public internet; 'internal' — VPC only; 'internal-and-cloud-load-balancing' — forces traffic through the HTTPS Load Balancer. |
vpc_egress_setting | 5 | 'PRIVATE_RANGES_ONLY' | 'PRIVATE_RANGES_ONLY' routes only RFC 1918 traffic via VPC. 'ALL_TRAFFIC' routes all egress via VPC. |
E. Initialization & Bootstrap
A db-init Cloud Run Job is automatically provisioned by Paperless Common when initialization_jobs is left as the default empty list ([]). It uses a PostgreSQL client image and executes Paperless_Common/scripts/db-init.sh, which performs the following idempotent operations:
- Connects to Cloud SQL PostgreSQL via the Auth Proxy Unix socket.
- Creates the
paperlessdatabase user with the password from Secret Manager. - Creates the
paperlessdatabase if it does not exist. - Grants the application user full privileges on the database.
Override initialization_jobs with a non-empty list to replace this default with custom jobs. When initialization_jobs is non-empty, Paperless Common does not inject the default db-init job.
Additional recurring cron jobs and additional sidecar Cloud Run services (e.g., Gotenberg for office document conversion, Tika for content extraction) can be configured via cron_jobs and additional_services:
| Variable | Group | Default | Description |
|---|---|---|---|
initialization_jobs | 13 | [] | One-shot Cloud Run Jobs. Leave empty for Paperless Common to supply the default db-init job. |
cron_jobs | 13 | [] | Recurring jobs triggered by Cloud Scheduler. |
additional_services | 13 | [] | Additional Cloud Run services deployed alongside Paperless-ngx. Use to add Gotenberg or Tika. |
4. Advanced Security
A. Cloud Armor WAF
When enable_cloud_armor = true, a Global HTTPS Load Balancer with a Cloud Armor WAF policy (OWASP Top 10, adaptive DDoS, 500 req/min rate limiting) is provisioned in front of Cloud Run. Document management systems containing sensitive files are strong candidates for WAF protection.
| Variable | Group | Default | Description |
|---|---|---|---|
enable_cloud_armor | 10 | false | Provisions Global HTTPS LB + Cloud Armor WAF. Required for custom domains, CDN, and DDoS protection. |
admin_ip_ranges | 10 | [] | CIDR ranges exempted from WAF rules (e.g., office VPN). |
B. Identity-Aware Proxy (IAP)
When enable_iap = true, Cloud Run's native IAP integration is enabled directly on the service. Google identity authentication is required before requests reach Paperless-ngx. Recommended for internal document archives where access should be restricted to authenticated organisational users.
| Variable | Group | Default | Description |
|---|---|---|---|
enable_iap | 5 | false | Enables IAP natively on the Cloud Run service. |
iap_authorized_users | 5 | [] | Users/service accounts granted access. Format: 'user:email' or 'serviceAccount:sa@...'. |
iap_authorized_groups | 5 | [] | Google Groups granted access. Format: 'group:name@example.com'. |
C. Binary Authorization
When enable_binary_authorization = true, Cloud Run enforces that deployed images carry a valid cryptographic attestation.
| Variable | Group | Default | Description |
|---|---|---|---|
enable_binary_authorization | 8 | false | Enforces image attestation. Requires a Binary Authorization policy and attestor pre-configured in the project. |
D. VPC Service Controls
When enable_vpc_sc = true, all GCP API calls from this module are bound within an existing VPC-SC perimeter.
| Variable | Group | Default | Description |
|---|---|---|---|
enable_vpc_sc | 22 | false | Registers module API calls within the project's VPC-SC perimeter. A perimeter must already exist before enabling. |
E. Secret Manager Integration
Paperless-ngx application secrets are stored in Secret Manager and injected natively by Cloud Run at revision start — plaintext is never written to Terraform state.
Paperless Common auto-generates PAPERLESS_ADMIN_PASSWORD and PAPERLESS_SECRET_KEY. User-defined secrets can be added via secret_environment_variables.
| Variable | Group | Default | Description |
|---|---|---|---|
secret_environment_variables | 6 | {} | Map of env var name → Secret Manager secret ID. Resolved at runtime. |
secret_rotation_period | 6 | '2592000s' | Frequency at which Secret Manager emits rotation notifications. Default: 30 days. |
secret_propagation_delay | 6 | 30 | Seconds to wait after secret creation before dependent resources proceed. |
5. Traffic & Ingress
A. HTTPS Load Balancer
When enable_cloud_armor = true, a Global HTTPS Load Balancer backed by a Serverless NEG is provisioned. Traffic flows: Internet → Cloud Armor → Global HTTPS LB → Serverless NEG → Cloud Run.
Setting ingress_settings = 'internal-and-cloud-load-balancing' forces all Paperless-ngx traffic through the LB, preventing direct *.run.app URL access.
B. Cloud CDN
When enable_cdn = true (requires enable_cloud_armor = true), Cloud CDN is attached to the HTTPS Load Balancer backend.
Paperless-ngx consideration: Paperless-ngx serves mostly authenticated, user-specific content (document lists, previews, download links). CDN caching is most beneficial for static assets (CSS, JS, thumbnails). Ensure authenticated document responses include Cache-Control: private headers before enabling CDN to prevent cross-user cache poisoning.
| Variable | Group | Default | Description |
|---|---|---|---|
enable_cdn | 10 | false | Enables Cloud CDN on the HTTPS LB backend. Only effective when enable_cloud_armor = true. |
max_images_to_retain | 10 | 7 | Maximum number of recent container images to keep in Artifact Registry. Set 0 to disable. |
delete_untagged_images | 10 | true | Automatically deletes untagged images from Artifact Registry. |
image_retention_days | 10 | 30 | Days after which images are eligible for deletion. |
C. Custom Domains
| Variable | Group | Default | Description |
|---|---|---|---|
application_domains | 10 | [] | Custom domain names for the HTTPS LB. Google-managed SSL certificates provisioned per domain. |
After the first apply, retrieve the LB IP from the Terraform output load_balancer_ip and create an A record. SSL certificate provisioning takes 10–30 minutes after DNS propagation.
6. CI/CD & Delivery
A. Cloud Build Triggers
When enable_cicd_trigger = true, a Cloud Build GitHub connection and push trigger are provisioned. The trigger builds and deploys a custom Paperless-ngx image when code is pushed to the configured branch.
| Variable | Group | Default | Description |
|---|---|---|---|
enable_cicd_trigger | 8 | false | Provisions a Cloud Build GitHub trigger. |
github_repository_url | 8 | "" | Full HTTPS URL of the GitHub repository. |
github_token | 8 | "" | GitHub PAT. Required on first apply. Sensitive. |
github_app_installation_id | 8 | "" | GitHub App installation ID (preferred for organisation repos). |
cicd_trigger_config | 8 | { branch_pattern = "^main$" } | Advanced trigger config: branch_pattern, included_files, ignored_files, trigger_name, substitutions. |
B. Cloud Deploy Pipeline
When enable_cloud_deploy = true (requires enable_cicd_trigger = true), the CI/CD pipeline is upgraded to a managed Cloud Deploy delivery pipeline with sequential promotion stages.
| Variable | Group | Default | Description |
|---|---|---|---|
enable_cloud_deploy | 8 | false | Provisions a Cloud Deploy pipeline. Requires enable_cicd_trigger = true. |
cloud_deploy_stages | 8 | [dev, staging, prod(approval)] | Ordered promotion stages. Each: name, target_name, service_name, require_approval, auto_promote. |
7. Reliability & Scheduling
A. Scaling & Concurrency
min_instance_count = 0 and max_instance_count = 3 are configurable via variables (unlike Ghost's hardcoded values). Paperless-ngx OCR tasks run asynchronously via Celery workers backed by Redis — the web UI remains responsive during heavy OCR jobs. Multiple Cloud Run instances can run concurrently with documents stored in GCS.
Consumption pipeline note: The background document consumption pipeline requires at least one running instance. Setting
min_instance_count = 0means uploaded documents will not be processed until the next request warms the instance.
B. Traffic Splitting
| Variable | Group | Default | Description |
|---|---|---|---|
traffic_split | 4 | [] | Percentage-based traffic allocation across named revisions. All entries must sum to 100. Empty sends 100% to the latest revision. |
C. Health Probes & Uptime Monitoring
Both startup and liveness probes target / (Paperless-ngx's login page), which returns HTTP 200 when the application is fully initialised. Paperless-ngx performs database migrations and starts Celery workers on first boot — the startup probe allows 60 seconds of initial delay with 60 failure thresholds before giving up.
| Variable | Group | Default | Description |
|---|---|---|---|
startup_probe | 14 | { enabled=true, type="HTTP", path="/", initial_delay_seconds=60, timeout_seconds=10, period_seconds=10, failure_threshold=60 } | Startup probe. 60 × 10s = 600s total allowance for first boot. |
liveness_probe | 14 | { enabled=true, type="HTTP", path="/", initial_delay_seconds=60, timeout_seconds=10, period_seconds=30, failure_threshold=3 } | Liveness probe. Container is restarted after 3 consecutive failures. |
uptime_check_config | 14 | { enabled=false, path="/" } | Optional Cloud Monitoring uptime check. Alerts notify support_users if unreachable. |
alert_policies | 14 | [] | Cloud Monitoring metric alert policies. |
D. Auto Password Rotation
When enable_auto_password_rotation = true, a zero-downtime password rotation pipeline is provisioned:
- Secret Manager emits a rotation notification at every
secret_rotation_periodinterval. - Eventarc fires a Cloud Run rotation Job.
- The job generates a new password, updates the Cloud SQL PostgreSQL user, and writes a new secret version.
- After
rotation_propagation_delay_secseconds, the job restarts the Paperless-ngx service.
| Variable | Group | Default | Description |
|---|---|---|---|
enable_auto_password_rotation | 12 | false | Enables automated password rotation. |
rotation_propagation_delay_sec | 12 | 90 | Seconds to wait after writing the new secret before restarting. |
8. Integrations
A. Redis (Required)
Redis is required and enabled by default (enable_redis = true). Paperless-ngx uses Redis as its Celery message broker — background OCR processing, document classification, consumption pipeline tasks, and asynchronous API calls all depend on Redis. Paperless-ngx will fail to start without a reachable Redis instance.
When enable_redis = true and redis_host is not provided, the module defaults to using the NFS server IP as the Redis host (a lightweight Redis instance co-located on the NFS GCE VM). For production deployments, point redis_host at a dedicated Google Cloud Memorystore for Redis instance.
| Variable | Group | Default | Description |
|---|---|---|---|
enable_redis | 21 | true | Required. Redis for Celery task queue and result backend. |
redis_host | 21 | "" | Redis server hostname or IP. Defaults to NFS server IP when empty. Override with Memorystore for production. |
redis_port | 21 | '6379' | Redis server TCP port (string). |
redis_auth | 21 | "" | Redis AUTH password. Leave empty if Redis does not require authentication. Sensitive. |
B. Paperless-ngx Application Settings
Paperless-ngx exposes several application-level settings as first-class variables. These are injected as environment variables by Paperless Common.
| Variable | Group | Default | Description |
|---|---|---|---|
time_zone | 15 | 'UTC' | Timezone for document timestamps and scheduled tasks. Important for correct date parsing during OCR. Use a valid TZ identifier (e.g., 'Europe/London', 'America/New_York'). |
ocr_language | 15 | 'eng' | Tesseract OCR language code. Combine multiple with + (e.g., 'eng+deu+fra'). Language packs must be installed in the container image. |
admin_user | 15 | 'admin' | Username for the auto-created superuser on first startup. |
admin_email | 15 | 'admin@example.com' | Email for the auto-created superuser account. |
OCR language codes: Use ISO 639-2/T codes as used by Tesseract. Common values: eng (English), deu (German), fra (French), spa (Spanish), ita (Italian), nld (Dutch), por (Portuguese). The language pack must be present in the container — the default image includes a standard set; custom builds can add additional packs.
C. Tika & Gotenberg (Optional)
Paperless-ngx optionally integrates with Tika (Apache) for content extraction from office documents and Gotenberg for document format conversion. These are disabled by default (PAPERLESS_TIKA_ENABLED=false).
To enable, add them as additional Cloud Run services via additional_services and set PAPERLESS_TIKA_ENABLED=true in environment_variables:
additional_services = [
{
name = "gotenberg"
image = "gotenberg/gotenberg:8"
port = 3000
cpu_limit = "1000m"
memory_limit = "512Mi"
ingress = "INGRESS_TRAFFIC_ALL"
output_env_var_name = "PAPERLESS_TIKA_GOTENBERG_ENDPOINT"
},
{
name = "tika"
image = "apache/tika:latest"
port = 9998
cpu_limit = "1000m"
memory_limit = "512Mi"
ingress = "INGRESS_TRAFFIC_ALL"
output_env_var_name = "PAPERLESS_TIKA_ENDPOINT"
}
]
environment_variables = {
PAPERLESS_TIKA_ENABLED = "true"
}
D. Backup Import & Recovery
When enable_backup_import = true, a dedicated Cloud Run Job restores an existing database backup into the provisioned Cloud SQL PostgreSQL instance. This runs after the db-init job.
| Variable | Group | Default | Description |
|---|---|---|---|
backup_schedule | 7 | '0 2 * * *' | Cron expression (UTC) for automated daily backups. |
backup_retention_days | 7 | 7 | Days to retain backup files in GCS. |
enable_backup_import | 7 | false | Triggers a one-time restore on apply. Set false after a successful import. |
backup_source | 7 | 'gcs' | 'gcs' (full GCS URI) or 'gdrive' (Drive file ID). |
backup_uri | 7 | "" | Full GCS URI (e.g., 'gs://my-bucket/paperless-backup.sql') or Google Drive file ID. |
backup_format | 7 | 'sql' | Backup format. Options: sql, tar, gz, tgz, tar.gz, zip. |
E. Observability & Alerting
A Cloud Monitoring uptime check polls the Paperless-ngx endpoint from multiple global locations. Custom alert policies can monitor Cloud Run metrics (latency, error rate, instance count) and notify support_users.
| Variable | Group | Default | Description |
|---|---|---|---|
uptime_check_config | 14 | { enabled=false, path="/" } | Uptime check configuration. Disabled by default. |
alert_policies | 14 | [] | Metric alert policies. Each: name, metric_type, comparison, threshold_value, duration_seconds, aggregation_period. |
9. Exploring with the GCP Console
After a successful deployment, the following GCP Console areas provide the most insight into a running Paperless-ngx instance.
Cloud Run — Service Details Navigate to Cloud Run → Services → paperless-<deployment-id>. The Revisions tab shows all deployed revisions, their traffic percentage, and the container image tag. Click a revision to see its environment variables (redacted for secrets) and resource limits. The Logs tab streams combined stdout/stderr from the gunicorn server and the Celery worker.
Cloud Run — Service URL
The URL field on the service overview is the direct *.run.app address for Paperless-ngx. Open it in a browser to reach the login page. Log in with the admin_user username and the password retrieved from Secret Manager (see §11).
Secret Manager
Navigate to Security → Secret Manager. Filter by the application_name prefix. You will see:
paperless-admin-password-<deployment-id>— the initial admin password.paperless-secret-key-<deployment-id>— the Django application secret key.paperless-db-password-<deployment-id>— the PostgreSQL application user password.
Click any secret and select View secret value (latest version) to retrieve the credential.
Cloud SQL
Navigate to SQL → Instances. The Paperless-ngx instance is named app<name><tenant><id> following the repo naming convention. Click the instance, then:
- Databases tab: confirms the
paperlessdatabase was created bydb-init. - Users tab: confirms the
paperlessuser was created. - Connections tab: shows active connections from the Cloud Run Auth Proxy sidecar.
- Operations tab: shows the history of all Cloud SQL operations, including database and user creation.
Cloud Storage
Navigate to Cloud Storage → Buckets. The paperless-media-<deployment-id> bucket holds all persistent document data: processed PDFs, originals, thumbnails, and OCR-extracted text. Browse the bucket to verify documents are being stored. The bucket uses STANDARD storage class with public access prevention enforced.
Artifact Registry
Navigate to Artifact Registry → Repositories. The paperless repository stores the custom Paperless-ngx container image. The Images tab shows tagged versions with timestamps and digest hashes.
Cloud Monitoring — Uptime Checks
Navigate to Monitoring → Uptime checks. The uptime check for the Paperless-ngx login page shows availability across global check locations and response times. Failures here alert support_users via email.
Cloud Build
Navigate to Cloud Build → History. Each deployment run appears here with build logs. The db-init Cloud Run Job execution is triggered as part of the apply pipeline.
10. Exploring with gcloud
The following commands are useful for inspecting and operating a Paperless-ngx Cloud Run deployment. Replace PROJECT_ID, REGION, and DEPLOYMENT_ID with your values.
# List all Cloud Run services in the project
gcloud run services list \
--project=PROJECT_ID \
--region=REGION \
--format="table(name,status.url,status.latestReadyRevisionName,status.conditions[0].status)"
# Describe the Paperless-ngx service and show the current URL
gcloud run services describe paperless-DEPLOYMENT_ID \
--project=PROJECT_ID \
--region=REGION \
--format="value(status.url)"
# Stream live logs from the Paperless-ngx service (last 5 minutes)
gcloud logging read \
'resource.type="cloud_run_revision" AND resource.labels.service_name="paperless-DEPLOYMENT_ID"' \
--project=PROJECT_ID \
--freshness=5m \
--format="table(timestamp,textPayload)" \
--order=asc
# List all revisions of the Paperless-ngx service
gcloud run revisions list \
--project=PROJECT_ID \
--region=REGION \
--service=paperless-DEPLOYMENT_ID \
--format="table(name,status.conditions[0].status,spec.containers[0].image,metadata.creationTimestamp)"
# Get the admin password from Secret Manager
gcloud secrets versions access latest \
--secret="paperless-admin-password-DEPLOYMENT_ID" \
--project=PROJECT_ID
# Get the Django secret key from Secret Manager
gcloud secrets versions access latest \
--secret="paperless-secret-key-DEPLOYMENT_ID" \
--project=PROJECT_ID
# List all secrets created for this deployment
gcloud secrets list \
--project=PROJECT_ID \
--filter="name:paperless" \
--format="table(name,replication.automatic,createTime)"
# Describe the Cloud SQL instance
gcloud sql instances describe APP_SQL_INSTANCE_NAME \
--project=PROJECT_ID \
--format="table(name,databaseVersion,settings.tier,ipAddresses[0].ipAddress,state)"
# List databases on the Cloud SQL instance
gcloud sql databases list \
--instance=APP_SQL_INSTANCE_NAME \
--project=PROJECT_ID
# List Cloud SQL users
gcloud sql users list \
--instance=APP_SQL_INSTANCE_NAME \
--project=PROJECT_ID
# List GCS buckets for the deployment (filter by name prefix)
gcloud storage buckets list \
--project=PROJECT_ID \
--filter="name:paperless" \
--format="table(name,location,storageClass,timeCreated)"
# List objects in the paperless-media bucket (document store)
gcloud storage ls gs://paperless-media-DEPLOYMENT_ID/
# List objects in the media/documents directory
gcloud storage ls gs://paperless-media-DEPLOYMENT_ID/documents/
# Check Memorystore Redis instance (if using dedicated Redis)
gcloud redis instances list \
--region=REGION \
--project=PROJECT_ID \
--format="table(name,host,port,state,memorySizeGb,authEnabled)"
# List Cloud Run Jobs (db-init and any custom jobs)
gcloud run jobs list \
--project=PROJECT_ID \
--region=REGION \
--format="table(name,status.latestCreatedExecution.name,metadata.creationTimestamp)"
# Execute the db-init job manually (e.g., for re-initialisation)
gcloud run jobs execute paperless-db-init-DEPLOYMENT_ID \
--project=PROJECT_ID \
--region=REGION
# Get the execution status of a Cloud Run Job
gcloud run jobs executions list \
--job=paperless-db-init-DEPLOYMENT_ID \
--project=PROJECT_ID \
--region=REGION \
--format="table(name,completionStatus,startTime,completionTime)"
# Check Cloud Monitoring uptime check results
gcloud monitoring uptime list-configs \
--project=PROJECT_ID \
--format="table(displayName,httpCheck.path,period,timeout)"
# List Artifact Registry images for the paperless repository
gcloud artifacts docker images list REGION-docker.pkg.dev/PROJECT_ID/paperless \
--project=PROJECT_ID \
--format="table(package,version,tags,createTime)"
# Update the Cloud Run service's traffic to a specific revision (canary)
gcloud run services update-traffic paperless-DEPLOYMENT_ID \
--project=PROJECT_ID \
--region=REGION \
--to-revisions=paperless-DEPLOYMENT_ID-REVISION=10,LATEST=90
# Send all traffic back to latest
gcloud run services update-traffic paperless-DEPLOYMENT_ID \
--project=PROJECT_ID \
--region=REGION \
--to-latest
# Check IAM bindings on the Cloud Run service
gcloud run services get-iam-policy paperless-DEPLOYMENT_ID \
--project=PROJECT_ID \
--region=REGION
11. Platform-Managed Behaviours
The following behaviours are applied automatically by Paperless CloudRun regardless of variable values.
| Behaviour | Implementation | Detail |
|---|---|---|
| PostgreSQL 15 required | database_type = "POSTGRES_15" set by Paperless Common | Paperless-ngx requires PostgreSQL. MySQL and SQLite are not supported. |
| Admin password auto-generated | PAPERLESS_ADMIN_PASSWORD secret provisioned by Paperless Common | The initial admin password is stored in Secret Manager. Retrieve it via gcloud secrets versions access latest --secret=paperless-admin-password-<id>. |
| Django secret key auto-generated | PAPERLESS_SECRET_KEY secret provisioned by Paperless Common | Required for Django session signing. Regenerating this key invalidates all existing user sessions. |
| GCS Fuse auto-mounted | paperless-media bucket mounted at /usr/src/paperless/media | When gcs_volumes = [], Paperless Common automatically provisions and mounts the media bucket. Requires gen2. |
| Redis required | enable_redis = true default | Paperless-ngx will not start without Redis. When redis_host = "", the NFS server IP is used. |
| min_instance_count = 0 | Default in variables.tf (scale-to-zero) | Cost-first default. Set to 1+ to keep the consumption pipeline alive without cold-start delay. |
| Image mirroring enabled | enable_image_mirroring = true default | GHCR images are mirrored to Artifact Registry to avoid rate limits and to satisfy Binary Authorization requirements. |
| Default db-init job | Supplied by Paperless Common when initialization_jobs = [] | PostgreSQL database and user are created automatically. Override with a non-empty list to replace. |
| Scripts directory | scripts_dir = abspath("${module.paperless_app.path}/scripts") | Initialization scripts are sourced from Paperless Common, not from the deployment directory. |
12. Variable Reference
All user-configurable variables exposed by Paperless CloudRun, sorted by UI group.
| Variable | Group | Default | Description |
|---|---|---|---|
project_id | 1 | — | GCP project ID. Required. |
region | 1 | 'us-central1' | GCP region for resource deployment. |
tenant_deployment_id | 2 | 'demo' | Short suffix appended to all resource names. |
support_users | 2 | [] | Email addresses for monitoring alerts. |
resource_labels | 2 | {} | Labels applied to all provisioned resources. |
application_name | 3 | 'paperless' | Base resource name. Do not change after initial deployment. |
display_name | 3 | 'Paperless-ngx - Document Management System' | Human-readable name for dashboards. |
description | 3 | 'Paperless-ngx - open-source document management system...' | Service description. |
application_version | 3 | 'latest' | Container image tag. Pin to a specific version for production. |
deploy_application | 4 | true | Set false for infrastructure-only deployment. |
cpu_limit | 4 | '2000m' | CPU per instance. 2 vCPU recommended for OCR. |
memory_limit | 4 | '2Gi' | Memory per instance. |
min_instance_count | 4 | 0 | Minimum instances. Scale-to-zero by default; set to 1+ to keep consumption pipeline alive. |
max_instance_count | 4 | 3 | Maximum instances. Cost ceiling. |
container_port | 4 | 8000 | Paperless-ngx gunicorn port. |
execution_environment | 4 | 'gen2' | Gen2 required for GCS Fuse. |
timeout_seconds | 4 | 300 | Max request duration. Increase for large document OCR. |
enable_cloudsql_volume | 4 | true | Connects via Cloud SQL Auth Proxy Unix socket. |
enable_image_mirroring | 4 | true | Mirrors GHCR image to Artifact Registry. |
container_protocol | 4 | 'http1' | 'http1' or 'h2c'. |
traffic_split | 4 | [] | Canary/blue-green traffic allocation. |
max_revisions_to_retain | 4 | 7 | Maximum Cloud Run revisions to keep. |
cloudsql_volume_mount_path | 4 | '/cloudsql' | Container path for Auth Proxy Unix socket. |
service_annotations | 4 | {} | Advanced Cloud Run annotations. |
service_labels | 4 | {} | Labels applied to the Cloud Run service. |
ingress_settings | 5 | 'all' | 'all', 'internal', or 'internal-and-cloud-load-balancing'. |
vpc_egress_setting | 5 | 'PRIVATE_RANGES_ONLY' | 'PRIVATE_RANGES_ONLY' or 'ALL_TRAFFIC'. |
enable_iap | 5 | false | Enables IAP natively on the Cloud Run service. |
iap_authorized_users | 5 | [] | Users/SAs granted IAP access. |
iap_authorized_groups | 5 | [] | Google Groups granted IAP access. |
environment_variables | 6 | {} | Plain-text environment variables for Paperless-ngx configuration. |
secret_environment_variables | 6 | {} | Secret Manager references. |
secret_propagation_delay | 6 | 30 | Seconds to wait after secret creation. |
secret_rotation_period | 6 | '2592000s' | Secret Manager rotation notification frequency. |
backup_schedule | 7 | '0 2 * * *' | Cron expression (UTC) for automated backups. |
backup_retention_days | 7 | 7 | Days to retain backup files in GCS. |
enable_backup_import | 7 | false | Triggers a one-time restore on apply. |
backup_source | 7 | 'gcs' | 'gcs' (full URI) or 'gdrive' (file ID). |
backup_uri | 7 | "" | Full GCS URI or Google Drive file ID. |
backup_format | 7 | 'sql' | Backup format. Options: sql, tar, gz, tgz, tar.gz, zip. |
enable_cicd_trigger | 8 | false | Provisions a Cloud Build GitHub trigger. |
github_repository_url | 8 | "" | Full HTTPS URL of the GitHub repository. |
github_token | 8 | "" | GitHub PAT. Required on first apply. Sensitive. |
github_app_installation_id | 8 | "" | GitHub App installation ID. |
cicd_trigger_config | 8 | { branch_pattern = "^main$" } | Advanced Cloud Build trigger config. |
enable_cloud_deploy | 8 | false | Provisions a Cloud Deploy progressive delivery pipeline. |
cloud_deploy_stages | 8 | [dev, staging, prod(approval)] | Ordered Cloud Deploy promotion stages. |
enable_binary_authorization | 8 | false | Enforces image attestation on deployment. |
enable_custom_sql_scripts | 9 | false | Runs SQL scripts from GCS after provisioning. |
custom_sql_scripts_bucket | 9 | "" | GCS bucket containing SQL scripts. |
custom_sql_scripts_path | 9 | "" | Path prefix within the bucket. |
custom_sql_scripts_use_root | 9 | false | Run scripts as the root DB user. |
enable_cloud_armor | 10 | false | Provisions Global HTTPS LB + Cloud Armor WAF. |
admin_ip_ranges | 10 | [] | CIDR ranges exempted from WAF rules. |
application_domains | 10 | [] | Custom domains with Google-managed SSL certificates. |
enable_cdn | 10 | false | Enables Cloud CDN on the HTTPS LB backend. |
max_images_to_retain | 10 | 7 | Maximum recent container images in Artifact Registry. |
delete_untagged_images | 10 | true | Deletes untagged images from Artifact Registry. |
image_retention_days | 10 | 30 | Days after which images are eligible for deletion. |
create_cloud_storage | 11 | true | Set false to skip GCS bucket creation. |
storage_buckets | 11 | [] | Additional GCS buckets to provision. |
enable_nfs | 11 | true | Provisions NFS server. Used as Redis host when redis_host is blank. |
nfs_mount_path | 11 | '/mnt/nfs' | Container path for NFS mount. |
nfs_instance_name | 11 | "" | Existing NFS GCE VM name. Leave empty to auto-discover. |
nfs_instance_base_name | 11 | 'app-nfs' | Base name for inline NFS VM. |
gcs_volumes | 11 | [] | GCS Fuse volume mounts. Empty auto-mounts paperless-media at /usr/src/paperless/media. |
manage_storage_kms_iam | 11 | false | Creates CMEK KMS key for storage buckets. |
enable_artifact_registry_cmek | 11 | false | Creates Artifact Registry KMS key. |
database_type | 12 | 'POSTGRES_15' | PostgreSQL version. Do not change — Paperless-ngx requires PostgreSQL. |
db_name | 12 | 'paperless' | PostgreSQL database name. Do not change after initial deployment. |
db_user | 12 | 'paperless' | PostgreSQL application user. |
database_password_length | 12 | 32 | Auto-generated password length. Range: 16–64. |
enable_auto_password_rotation | 12 | false | Automated zero-downtime password rotation. |
rotation_propagation_delay_sec | 12 | 90 | Seconds to wait after rotation before restarting. |
initialization_jobs | 13 | [] | One-shot Cloud Run Jobs. Empty: Paperless Common supplies default db-init. |
cron_jobs | 13 | [] | Recurring scheduled Cloud Run Jobs. |
additional_services | 13 | [] | Additional Cloud Run services (e.g., Gotenberg, Tika). |
startup_probe | 14 | { path="/", initial_delay_seconds=60, failure_threshold=60, ... } | Startup probe. Long failure threshold for first-boot migrations. |
liveness_probe | 14 | { path="/", initial_delay_seconds=60, failure_threshold=3, ... } | Liveness probe. |
uptime_check_config | 14 | { enabled=false, path="/" } | Optional Cloud Monitoring uptime check. |
alert_policies | 14 | [] | Cloud Monitoring metric alert policies. |
time_zone | 15 | 'UTC' | Timezone for document timestamps. Important for OCR date parsing. |
ocr_language | 15 | 'eng' | Tesseract OCR language. Combine with + for multiple languages. |
admin_user | 15 | 'admin' | Initial admin account username. |
admin_email | 15 | 'admin@example.com' | Initial admin account email. |
enable_redis | 21 | true | Required. Redis for Celery task queue. |
redis_host | 21 | "" | Redis hostname/IP. Defaults to NFS server IP when empty. |
redis_port | 21 | '6379' | Redis TCP port (string). |
redis_auth | 21 | "" | Redis AUTH password. Sensitive. |
enable_vpc_sc | 22 | false | Registers API calls within the project's VPC-SC perimeter. |
vpc_cidr_ranges | 22 | [] | VPC subnet CIDR ranges for VPC-SC network access level. |
vpc_sc_dry_run | 22 | true | Logs VPC-SC violations without blocking. Set false to enforce. |
organization_id | 22 | "" | GCP Organization ID for VPC-SC. Auto-discovered when empty. |
enable_audit_logging | 22 | false | Enables detailed Cloud Audit Logs. |
13. Outputs
| Output | Description |
|---|---|
service_name | Name of the Cloud Run service. |
service_url | Public URL of the Cloud Run service. |
service_location | GCP region where the Cloud Run service is deployed. |
project_id | GCP project ID. |
deployment_id | Deployment ID suffix used in resource names. |
database_instance_name | Name of the Cloud SQL PostgreSQL instance. |
database_name | Name of the application database. |
database_user | Name of the application database user. |
database_password_secret | Secret Manager secret name for the database password. |
storage_buckets | Created GCS storage buckets. |
load_balancer_ip | Static IP address of the load balancer. |
load_balancer_url | HTTPS URL to access the application via the load balancer. |
container_image | Container image used for the deployment. |
cicd_enabled | Whether the CI/CD pipeline is enabled. |
github_repository_url | GitHub repository URL connected for CI/CD. |
Configuration Pitfalls & Sensible Defaults
Risk levels: Critical (data loss, full outage, security breach) — High (service unavailable or significant degradation) — Medium (degraded function or increased cost) — Low (minor impact).
| Variable | Sensible Default | Risk | Consequence of Incorrect Value |
|---|---|---|---|
project_id | (required) | Critical | No default — deployment fails immediately. |
database_type | "POSTGRES_15" | Critical | Paperless-ngx requires PostgreSQL. Changing to MySQL or NONE will cause a startup failure — Paperless-ngx's ORM is configured for PostgreSQL exclusively. |
enable_redis | true | Critical | Redis is mandatory. Without Redis, Paperless-ngx cannot start its Celery workers — the consumption pipeline, OCR processing, and document classification are all inoperable. Documents uploaded via the web UI will queue indefinitely. |
redis_host | "" (auto-resolves to NFS IP) | High | Relies on NFS server IP when blank. If enable_nfs = false and redis_host remains empty with enable_redis = true, Paperless-ngx will fail to connect to Redis at startup. |
enable_nfs | true | High | The NFS server IP is used as the default Redis host. Disabling NFS without providing an explicit redis_host causes Redis connection failures at startup. |
execution_environment | 'gen2' | Critical | GCS Fuse requires Gen2. Switching to 'gen1' while GCS volumes are mounted causes Cloud Run revisions to fail to start. The paperless-media GCS Fuse volume is always mounted when gcs_volumes = []. |
gcs_volumes | [] (auto-mounts paperless-media) | Critical | The paperless-media bucket is the sole persistent storage for all Paperless-ngx documents. If the auto-mount is overridden incorrectly, documents are written to the ephemeral container filesystem and lost on the next revision deployment or instance restart. |
min_instance_count | 0 (default; scale-to-zero) | High | When the instance is cold, uploaded documents are not consumed until the next request warms the service. For unattended consumption directory workflows, this can result in hours of unprocessed documents — set to 1+ if this matters. |
db_name | "paperless" | Critical | Immutable after first deployment — changing this causes Terraform to recreate the database, destroying all document metadata, tags, correspondents, and settings. Physical document files in GCS survive but become unreferenced. |
db_user | "paperless" | Critical | Immutable after first deployment — changing this recreates the Cloud SQL user and invalidates all stored credentials. |
application_version | "latest" | Medium | Using latest means each Cloud Build run may pull a new Paperless-ngx version, which can include database migrations or breaking changes. Pin to a specific version (e.g., "2.13.5") for production deployments. |
ocr_language | "eng" | Medium | Set to match the primary language of scanned documents. Incorrect language codes result in poor OCR quality and failed full-text indexing. Multiple languages ("eng+deu") increase OCR time. |
time_zone | "UTC" | Medium | Incorrect timezone causes document date parsing errors during OCR — dates on scanned documents may be assigned to the wrong day. Set to match your organisation's timezone. |
memory_limit | "2Gi" | High | OCR on multi-page, high-DPI scans requires significant memory. Reducing below 1Gi causes Python OOM errors during Tesseract processing. For heavy OCR workloads, 4Gi is recommended. |
timeout_seconds | 300 | Medium | OCR on large, multi-page PDFs can take longer than 300 seconds. Cloud Run will terminate requests exceeding timeout_seconds. Consider setting 600 or higher for archives containing large document batches. |
backup_retention_days | 7 | Medium | Seven days is insufficient for a document archive. Loss of more than 7 days of database changes means losing all document metadata (tags, correspondents, custom fields, notes) added during that period. Increase to 30+ days for production. |
enable_cloud_armor | false | Medium | Without Cloud Armor, the Paperless-ngx login page is exposed directly to the internet. Document management systems often contain sensitive financial and personal documents. WAF protection is strongly recommended for any internet-accessible deployment. |
enable_iap | false | Medium | IAP adds Google identity-based authentication in front of Paperless-ngx. For internal document archives, IAP prevents brute-force attacks against the Paperless-ngx login page entirely. |
startup_probe failure_threshold | 60 | High | Paperless-ngx applies database migrations on startup. 60 × 10s = 600 seconds total startup tolerance. Reducing failure_threshold below 30 causes Cloud Run to kill the container before migrations complete on first deploy, creating a restart loop. |
Destroying Resources
Known Deletion Issue: Serverless IPv4 Address Release
When destroying a Cloud Run deployment, you may encounter an error similar to:
Error: Error waiting for Subnetwork to be deleted: The following serverless IPv4 address(es) on subnet ... are still in use.
Cause: GCP holds serverless IPv4 addresses on the VPC subnet asynchronously after a Cloud Run service is deleted. These addresses are released by GCP approximately 20–30 minutes after the Cloud Run service is removed.
Resolution: Wait 20–30 minutes after the initial destroy attempt, then re-run the destroy command:
tofu destroy
The second run will succeed once GCP has released the reserved addresses.