n8n Self-Hosted Deployment Guide: Zero-Cost Automation Engine
Status: Production Deployment Guide
Research Focus: Self-Hosted n8n on Google Cloud Run for Maximum Cost Efficiency
Verified: Based on production patterns and real deployments
Executive Summary
n8n is the automation heart of our zero-cost architecture. By self-hosting n8n on Google Cloud Run instead of using n8n Cloud, we achieve unlimited workflow executions at ~$8/month base cost. This guide provides complete implementation details for deploying, optimizing, and monitoring a production-ready n8n instance that scales to zero when idle.
Why Self-Hosted n8n on Cloud Run
Key Benefits of Our Approach
| Aspect | n8n Cloud | VM Deployment | Cloud Run |
|---|---|---|---|
| Fixed Costs | $50-500/month | $40+/month | $8/month |
| Executions | Limited by plan | Unlimited | Unlimited |
| Scaling | Automatic | Manual | Auto + Scale-to-0 |
| Maintenance | None | High | Minimal |
| Cold Starts | None | None | 3-5 seconds |
Architecture Overview
1. n8n in the NudgeCampaign Stack
n8n's Role: The Automation Orchestrator
Integration Points:
- Triggers: Webhooks from Next.js API
- Data Source: Direct Cloud SQL access
- Email Delivery: Postmark API integration
- Monitoring: Google Cloud Logging & Monitoring
2. Core Components
# n8n Cloud Run Deployment Architecture
services:
n8n-app:
image: n8nio/n8n:latest
platform: Cloud Run
scaling:
min_instances: 0 # Scale to zero
max_instances: 10
concurrent_requests: 10
database:
type: Cloud SQL
tier: db-f1-micro # $8/month
connections: 25
storage:
workflows: PostgreSQL
credentials: Google Secret Manager
executions: PostgreSQL (with retention)
Step-by-Step Deployment
Phase 1: Environment Setup
Prerequisites
- Google Cloud Project with billing enabled
- Cloud SQL instance (existing $8/month db-f1-micro)
- Domain for n8n interface (subdomain recommended)
- Postmark account with API token
1.1 Create n8n Database
-- Connect to your existing Cloud SQL instance
CREATE DATABASE n8n_prod;
CREATE USER n8n_user WITH PASSWORD 'your-secure-password';
GRANT ALL PRIVILEGES ON DATABASE n8n_prod TO n8n_user;
-- Switch to n8n database
\c n8n_prod;
-- Grant schema permissions
GRANT ALL ON SCHEMA public TO n8n_user;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO n8n_user;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO n8n_user;
1.2 Configure Secret Manager
# Store sensitive configuration in Secret Manager
gcloud secrets create n8n-db-password --data-file=- <<< "your-secure-password"
gcloud secrets create n8n-encryption-key --data-file=- <<< "$(openssl rand -base64 32)"
gcloud secrets create postmark-token --data-file=- <<< "your-postmark-server-token"
# Grant Cloud Run access to secrets
gcloud secrets add-iam-policy-binding n8n-db-password \
--member="serviceAccount:your-cloudrun-sa@project.iam.gserviceaccount.com" \
--role="roles/secretmanager.secretAccessor"
Phase 2: Container Configuration
2.1 Create Dockerfile
# Dockerfile for n8n Cloud Run deployment
FROM n8nio/n8n:latest
# Install additional nodes if needed
# RUN npm install -g n8n-nodes-postmark
# Set ownership for security
USER node
# Health check for Cloud Run
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:5678/healthz || exit 1
# Expose port
EXPOSE 5678
# Use exec form for proper signal handling
CMD ["n8n", "start"]
2.2 Environment Configuration
# cloudbuild.yaml for automated deployment
steps:
- name: 'gcr.io/cloud-builders/docker'
args: ['build', '-t', 'gcr.io/$PROJECT_ID/n8n:$BUILD_ID', '.']
- name: 'gcr.io/cloud-builders/docker'
args: ['push', 'gcr.io/$PROJECT_ID/n8n:$BUILD_ID']
- name: 'gcr.io/cloud-builders/gcloud'
args:
- 'run'
- 'deploy'
- 'n8n-automation'
- '--image=gcr.io/$PROJECT_ID/n8n:$BUILD_ID'
- '--region=us-central1'
- '--platform=managed'
- '--allow-unauthenticated'
- '--port=5678'
- '--memory=1Gi'
- '--cpu=1'
- '--min-instances=0'
- '--max-instances=10'
- '--set-env-vars=NODE_ENV=production'
- '--set-secrets=DB_POSTGRESDB_PASSWORD=n8n-db-password:latest'
- '--set-secrets=N8N_ENCRYPTION_KEY=n8n-encryption-key:latest'
Phase 3: Cloud Run Service Definition
3.1 Complete Service Configuration
# service.yaml - Complete Cloud Run configuration
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: n8n-automation
annotations:
run.googleapis.com/ingress: all
run.googleapis.com/launch-stage: GA
spec:
template:
metadata:
annotations:
# Scaling configuration
autoscaling.knative.dev/minScale: "0"
autoscaling.knative.dev/maxScale: "10"
# Performance optimization
run.googleapis.com/cpu-throttling: "true"
run.googleapis.com/startup-cpu-boost: "true"
run.googleapis.com/execution-environment: gen2
# Connection timeout
run.googleapis.com/timeout: "900s" # 15 minutes for long workflows
spec:
containerConcurrency: 10 # Conservative for stability
serviceAccountName: n8n-service-account
containers:
- image: gcr.io/your-project/n8n:latest
ports:
- containerPort: 5678
resources:
limits:
cpu: "1"
memory: "1Gi"
requests:
cpu: "0.1" # Minimal for cold starts
memory: "256Mi"
env:
# Core n8n configuration
- name: NODE_ENV
value: "production"
- name: N8N_PROTOCOL
value: "https"
- name: N8N_HOST
value: "n8n.nudgecampaign.com"
- name: N8N_PORT
value: "5678"
- name: WEBHOOK_URL
value: "https://n8n.nudgecampaign.com"
# Database configuration
- name: DB_TYPE
value: "postgresdb"
- name: DB_POSTGRESDB_HOST
value: "your-cloud-sql-ip"
- name: DB_POSTGRESDB_PORT
value: "5432"
- name: DB_POSTGRESDB_DATABASE
value: "n8n_prod"
- name: DB_POSTGRESDB_USER
value: "n8n_user"
# Execution settings
- name: EXECUTIONS_PROCESS
value: "main" # Run in main process for Cloud Run
- name: EXECUTIONS_TIMEOUT
value: "300" # 5 minutes max per workflow
- name: EXECUTIONS_DATA_SAVE_ON_ERROR
value: "all"
- name: EXECUTIONS_DATA_SAVE_ON_SUCCESS
value: "all"
- name: EXECUTIONS_DATA_PRUNE
value: "true"
- name: EXECUTIONS_DATA_MAX_AGE
value: "168" # 7 days retention
# Performance tuning
- name: N8N_PAYLOAD_SIZE_MAX
value: "16" # 16MB max payload
- name: NODE_OPTIONS
value: "--max-old-space-size=1024"
# Security
- name: N8N_BASIC_AUTH_ACTIVE
value: "true"
- name: N8N_BASIC_AUTH_USER
value: "admin"
# Secrets from Secret Manager
- name: DB_POSTGRESDB_PASSWORD
valueFrom:
secretKeyRef:
name: n8n-db-password
key: latest
- name: N8N_ENCRYPTION_KEY
valueFrom:
secretKeyRef:
name: n8n-encryption-key
key: latest
- name: N8N_BASIC_AUTH_PASSWORD
valueFrom:
secretKeyRef:
name: n8n-admin-password
key: latest
3.2 Deploy with gcloud
# Deploy the service
gcloud run services replace service.yaml --region=us-central1
# Set up custom domain
gcloud run domain-mappings create \
--service=n8n-automation \
--domain=n8n.nudgecampaign.com \
--region=us-central1
Production Optimization
1. Database Connection Optimization
ποΈ Efficient Database Usage
n8n can be database-intensive. Optimize connections to minimize the Cloud SQL load and costs.
# Additional environment variables for database optimization
env:
# Connection pooling
- name: DB_POSTGRESDB_POOL_SIZE
value: "5" # Conservative pool size for serverless
# Connection timeouts
- name: DB_POSTGRESDB_POOL_CONNECTION_TIMEOUT_MILLIS
value: "3000"
- name: DB_POSTGRESDB_POOL_IDLE_TIMEOUT_MILLIS
value: "10000"
# SSL configuration
- name: DB_POSTGRESDB_SSL_CA
value: "/etc/ssl/certs/server-ca.pem"
- name: DB_POSTGRESDB_SSL_CERT
value: "/etc/ssl/certs/client-cert.pem"
- name: DB_POSTGRESDB_SSL_KEY
value: "/etc/ssl/private/client-key.pem"
- name: DB_POSTGRESDB_SSL_REJECT_UNAUTHORIZED
value: "false"
2. Workflow Performance Optimization
2.1 Efficient Workflow Patterns
// Example: Optimized bulk email sending workflow
{
"name": "Bulk Email Campaign - Optimized",
"nodes": [
{
"name": "Campaign Trigger",
"type": "webhook",
"parameters": {
"path": "campaign-send-v3",
"responseMode": "immediately"
}
},
{
"name": "Get Recipients Batch",
"type": "postgres",
"parameters": {
"operation": "executeQuery",
"query": `
SELECT email, first_name, last_name, preferences
FROM contacts
WHERE campaign_id = $1
AND status = 'active'
AND email_verified = true
LIMIT 500
`,
"additionalFields": {
"queryParams": "={{ $json.campaignId }}"
}
}
},
{
"name": "Batch Processor",
"type": "splitInBatches",
"parameters": {
"batchSize": 500 // Postmark's maximum
}
},
{
"name": "Personalize Content",
"type": "code",
"parameters": {
"language": "javascript",
"code": `
// Efficient personalization without external API calls
const template = $items[0].json.template;
const recipients = $items[1].json;
return recipients.map(recipient => ({
json: {
From: 'hello@nudgecampaign.com',
To: recipient.email,
Subject: template.subject.replace('{{firstName}}', recipient.first_name || 'there'),
HtmlBody: personalizeTemplate(template.html, recipient),
TextBody: personalizeTemplate(template.text, recipient),
MessageStream: "marketing",
Tag: \`campaign-\${$items[0].json.campaignId}\`,
Metadata: {
campaignId: $items[0].json.campaignId,
contactId: recipient.id,
batchId: $items[0].json.batchId
}
}
}));
function personalizeTemplate(template, recipient) {
return template
.replace(/{{firstName}}/g, recipient.first_name || 'there')
.replace(/{{lastName}}/g, recipient.last_name || '')
.replace(/{{email}}/g, recipient.email);
}
`
}
},
{
"name": "Send via Postmark",
"type": "postmark",
"parameters": {
"operation": "sendBatch",
"messages": "={{ $json }}"
}
},
{
"name": "Update Status",
"type": "postgres",
"parameters": {
"operation": "executeQuery",
"query": `
UPDATE contacts
SET
last_email_sent = NOW(),
email_count = email_count + 1,
campaign_status = 'sent'
WHERE id = ANY($1)
`,
"additionalFields": {
"queryParams": "={{ $json.map(item => item.Metadata.contactId) }}"
}
}
}
]
}
2.2 Error Handling & Retry Logic
// Robust error handling node
{
"name": "Error Handler",
"type": "code",
"parameters": {
"language": "javascript",
"code": `
const error = $items[0].json;
// Classify error types
const errorType = classifyError(error);
switch (errorType) {
case 'RATE_LIMIT':
// Exponential backoff
const delay = Math.min(300000, Math.pow(2, error.retryCount || 0) * 1000);
return [{ json: { action: 'retry', delay, error } }];
case 'TEMPORARY':
// Simple retry
return [{ json: { action: 'retry', delay: 30000, error } }];
case 'PERMANENT':
// Log and skip
return [{ json: { action: 'skip', reason: error.message } }];
default:
// Unknown error - retry once
if ((error.retryCount || 0) < 1) {
return [{ json: { action: 'retry', delay: 10000, error } }];
} else {
return [{ json: { action: 'fail', error } }];
}
}
function classifyError(error) {
if (error.message.includes('rate limit')) return 'RATE_LIMIT';
if (error.message.includes('timeout')) return 'TEMPORARY';
if (error.message.includes('invalid email')) return 'PERMANENT';
return 'UNKNOWN';
}
`
}
}
3. Cold Start Optimization
# Dockerfile optimization for faster cold starts
FROM n8nio/n8n:latest
# Pre-warm node modules
RUN npm install --production --silent
# Minimize layers
COPY --chown=node:node . /home/node/.n8n/
# Use multi-stage build for smaller image
FROM node:18-alpine
COPY --from=0 /usr/local/lib/node_modules/n8n /usr/local/lib/node_modules/n8n
COPY --from=0 /usr/local/bin/n8n /usr/local/bin/n8n
USER node
CMD ["n8n", "start"]
Monitoring & Observability
1. Cloud Run Metrics
Key Metrics to Monitor
- Cold Start Frequency: Should be <5% of requests
- Execution Time: Average workflow completion time
- Error Rate: Failed workflows percentage
- Database Connections: Active connection count
- Memory Usage: Container memory utilization
# Cloud Monitoring dashboard configuration
resources:
- name: "n8n-dashboard"
type: monitoring.dashboard
properties:
displayName: "n8n Automation Metrics"
mosaicLayout:
tiles:
- width: 6
height: 4
widget:
title: "Cold Start Rate"
scorecard:
timeSeriesQuery:
timeSeriesFilter:
filter: 'resource.type="cloud_run_revision"'
metric: 'run.googleapis.com/container/startup_latencies'
- width: 6
height: 4
widget:
title: "Active Instances"
scorecard:
timeSeriesQuery:
timeSeriesFilter:
filter: 'resource.type="cloud_run_revision"'
metric: 'run.googleapis.com/container/instance_count'
2. Custom Application Metrics
// Add to n8n startup script for custom metrics
const { MeterProvider } = require('@opentelemetry/sdk-metrics');
const { Resource } = require('@opentelemetry/resources');
const { PrometheusExporterWrapper } = require('@opentelemetry/exporter-prometheus');
const meterProvider = new MeterProvider({
resource: new Resource({
'service.name': 'n8n-automation',
'service.version': process.env.N8N_VERSION
})
});
const meter = meterProvider.getMeter('n8n-custom');
// Workflow execution metrics
const workflowExecutions = meter.createCounter('n8n_workflow_executions', {
description: 'Number of workflow executions'
});
const workflowDuration = meter.createHistogram('n8n_workflow_duration', {
description: 'Workflow execution duration in milliseconds'
});
// Export metrics for Cloud Monitoring
const exporter = new PrometheusExporterWrapper({
port: 9090
});
meterProvider.addMetricReader(exporter);
3. Alerting Configuration
# Cloud Monitoring alert policies
alerting:
policies:
- displayName: "n8n High Error Rate"
conditions:
- displayName: "Error rate > 5%"
conditionThreshold:
filter: 'resource.type="cloud_run_revision" AND resource.label.service_name="n8n-automation"'
comparison: COMPARISON_GREATER_THAN
thresholdValue: 0.05
duration: 300s
notificationChannels:
- "projects/your-project/notificationChannels/slack-alerts"
- displayName: "n8n Container Memory High"
conditions:
- displayName: "Memory usage > 80%"
conditionThreshold:
filter: 'resource.type="cloud_run_revision"'
metric: 'run.googleapis.com/container/memory/utilizations'
comparison: COMPARISON_GREATER_THAN
thresholdValue: 0.8
Cost Optimization Strategies
1. Execution Data Retention
# Optimize execution history storage
env:
- name: EXECUTIONS_DATA_PRUNE
value: "true"
- name: EXECUTIONS_DATA_MAX_AGE
value: "72" # 3 days for production
- name: EXECUTIONS_DATA_SAVE_ON_SUCCESS
value: "none" # Only save errors
- name: EXECUTIONS_DATA_SAVE_ON_ERROR
value: "all" # Keep errors for debugging
2. Efficient Resource Usage
# Right-size containers based on actual usage
gcloud run services update n8n-automation \
--memory=512Mi \
--cpu=0.5 \
--concurrency=5 \
--max-instances=5 \
--region=us-central1
3. Database Query Optimization
-- Optimize n8n database with indexes
CREATE INDEX CONCURRENTLY idx_executions_finished_at
ON executions_entity (finished_at)
WHERE finished_at IS NOT NULL;
CREATE INDEX CONCURRENTLY idx_executions_workflow_id
ON executions_entity (workflow_id, finished_at);
-- Clean up old executions automatically
CREATE OR REPLACE FUNCTION cleanup_old_executions()
RETURNS void AS $
BEGIN
DELETE FROM executions_entity
WHERE finished_at < NOW() - INTERVAL '7 days';
END;
$ LANGUAGE plpgsql;
-- Schedule cleanup (run from cron job)
SELECT cron.schedule('cleanup-executions', '0 2 * * *', 'SELECT cleanup_old_executions();');
Production Checklist
Pre-Launch
- Database Setup: n8n database created with proper permissions
- Secrets Management: All credentials stored in Secret Manager
- SSL Configuration: HTTPS enabled with custom domain
- Basic Auth: Admin interface secured
- Resource Limits: CPU/Memory configured appropriately
- Health Checks: Container health monitoring enabled
Monitoring
- Alerting: Error rate and performance alerts configured
- Logging: Structured logging enabled
- Metrics: Custom application metrics implemented
- Dashboard: Cloud Monitoring dashboard created
- Budget Alerts: Cost monitoring alerts set up
Optimization
- Cold Starts: Optimized for sub-5-second startup
- Database: Connection pooling configured
- Executions: Retention policy implemented
- Workflows: Batch processing patterns adopted
- Error Handling: Robust retry logic implemented
Conclusion
Self-hosting n8n on Google Cloud Run provides the perfect balance of cost efficiency and operational simplicity:
~$8/month base cost (vs $50-500 for n8n Cloud)
Unlimited workflow executions
Auto-scaling from 0 to 10 instances
Production-ready reliability
Complete control over configuration
Result: Enterprise-grade automation at indie hacker prices
This deployment strategy allows n8n to serve as the automation brain of your email marketing platform while maintaining the zero-fixed-cost philosophy that makes your business model sustainable from day one.
Related Documents
- Technology Stack Analysis - Overall technology decisions
- Integration Patterns - How n8n connects with other components
- Cost Optimization Strategy - Detailed cost reduction techniques
n8n deployment guide based on production implementations and 2025 Google Cloud pricing. Last updated: 2025-07-28