developer

How to Deploy a Production-Ready Payment Gateway on AWS or DigitalOcean

Step-by-step production deployment guide for self-hosted UPI payment gateways using Docker Compose, Caddy TLS, PostgreSQL, and Redis caching.

GS Gaurav Sharma Fintech Architect & Systems Engineer 2 min read
How to Deploy a Production-Ready Payment Gateway on AWS or DigitalOcean guide
deploy payment gateway docker compose self hosted upi server setup fintech infra on digitalocean docker payment microservices caddy reverse proxy

Deploying private payment infrastructure requires high-availability guarantees, immutable logging, encrypted traffic, and isolated microservice boundaries. By utilizing Docker Compose alongside a modern reverse proxy like Caddy, developers can deploy a robust, PCI-compliant, zero-downtime payment switch on AWS EC2 or DigitalOcean in under thirty minutes.


Production Infrastructure Requirements

Before launching containers, verify that your host server meets baseline security and performance standards:

  • Host Operating System: Ubuntu 24.04 LTS x64 (Minimal installation)
  • Minimum Compute: 4 vCPUs, 8 GB ECC RAM, 100 GB NVMe Storage
  • Firewall Configuration (UFW):
    • Inbound Port 80 (HTTP redirect to HTTPS)
    • Inbound Port 443 (HTTPS secure payment traffic)
    • Inbound Port 22 (SSH restricted to internal VPN IP addresses)
    • All internal microservice ports (5432, 6379, 3000) bound exclusively to the private Docker bridge network

Docker Compose Production Stack

Below is the production-grade multi-container topology configured for seamless scaling:

# docker-compose.prod.yml
version: '3.8'

services:
  caddy:
    image: caddy:2.8-alpine
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    networks:
      - gateway_net
    depends_on:
      - api-gateway

  api-gateway:
    image: vyapargateway/core-switch:latest
    restart: always
    env_file: .env.production
    environment:
      NODE_ENV: production
      PORT: 3000
      DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}?sslmode=disable
      REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379/0
    networks:
      - gateway_net
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 4096M

  postgres:
    image: postgres:16-alpine
    restart: always
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}
    volumes:
      - pg_data:/var/lib/postgresql/data
    networks:
      - gateway_net
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
      interval: 5s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7.2-alpine
    restart: always
    command: redis-server --requirepass ${REDIS_PASSWORD} --appendonly yes
    volumes:
      - redis_data:/data
    networks:
      - gateway_net
    healthcheck:
      test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
      interval: 5s
      timeout: 5s
      retries: 5

networks:
  gateway_net:
    driver: bridge

volumes:
  caddy_data:
  caddy_config:
  pg_data:
  redis_data:

Caddy Reverse Proxy & Auto-TLS Configuration

Caddy automatically provisions and renews TLS certificates from Let’s Encrypt or ZeroSSL without requiring cron scripts or certbot interventions:

# Caddyfile
api.yourdomain.com {
    encode zstd gzip

    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
        X-Content-Type-Options "nosniff"
        X-Frame-Options "DENY"
        Referrer-Policy "strict-origin-when-cross-origin"
    }

    reverse_proxy api-gateway:3000 {
        header_up Host {host}
        header_up X-Real-IP {remote_host}
        header_up X-Forwarded-For {remote_host}
        header_up X-Forwarded-Proto {scheme}
    }
}

Database Tuning & Persistence

Default PostgreSQL settings are tuned for low-memory environments. For a high-speed payment ledger on an 8 GB host, mount a custom postgresql.conf tuning parameters for write-heavy transactions:

# postgresql.conf snippet for payment switches
shared_buffers = 2GB
effective_cache_size = 6GB
maintenance_work_mem = 512MB
checkpoint_completion_target = 0.9
wal_buffers = 16MB
default_statistics_target = 100
random_page_cost = 1.1
effective_io_concurrency = 200
work_mem = 10485kB
min_wal_size = 1GB
max_wal_size = 4GB

Automated Backup and Failover Runbook

Payment transaction tables must be preserved with zero data loss. Schedule an hourly automated encrypted backup script using WAL archiving and S3 offsite replication:

#!/bin/bash
# /opt/scripts/backup_ledger.sh
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
BACKUP_DIR="/opt/backups"
FILENAME="pg_dump_ledger_${TIMESTAMP}.sql.gz"

# Perform compressed dump
docker exec -t $(docker ps -qf "name=postgres") pg_dump -U $POSTGRES_USER $POSTGRES_DB | gzip > "${BACKUP_DIR}/${FILENAME}"

# Encrypt with OpenSSL using server key
openssl enc -aes-256-cbc -salt -in "${BACKUP_DIR}/${FILENAME}" -out "${BACKUP_DIR}/${FILENAME}.enc" -pass file:/etc/ssl/backup.key

# Sync to encrypted offsite cloud storage
aws s3 cp "${BACKUP_DIR}/${FILENAME}.enc" s3://fintech-cold-backups/daily/

# Remove local unencrypted file
rm "${BACKUP_DIR}/${FILENAME}"

System Health and Telemetry Monitoring

To ensure SLA compliance:

  1. Prometheus Node Exporter: Monitored on localhost for memory starvation and CPU thread saturation.
  2. Webhook Health Endpoints: Periodic external pinging of /v1/health checking database connection pool viability.
  3. Log Aggregation: Standard Docker stdout shipping to Loki or CloudWatch logs with sensitive payload masking enabled.

Self-hosting your payment gateway provides sovereign control over transaction ledgers, eliminates third-party platform lock-in, and reduces operational infrastructure overhead to a predictable server hosting bill.

Direct answers

Frequently asked questions

What VPS hardware specifications are recommended for self-hosting a payment switch?
For processing up to 20,000 daily UPI transactions, a standard 4 vCPU, 8 GB RAM instance (such as DigitalOcean Premium Droplet or AWS EC2 c6i.xlarge) with NVMe SSD storage delivers sub-15ms webhook execution and reliable Redis pub/sub queueing.
Why use Caddy instead of Nginx for fintech container routing?
Caddy provides automated zero-configuration Let's Encrypt TLS provisioning, native HTTP/3 support, memory-safe Go concurrency, and dynamic reverse-proxy health checks out of the box with zero downtime SSL certificate renewal.
How are PostgreSQL database credentials and API secrets protected in production?
Production secrets should never be committed into docker-compose files. Store sensitive variables in an external `.env.production` file with strict POSIX file permissions (chmod 600) or inject them via AWS Secrets Manager or HashiCorp Vault during container orchestration.

Build your payment flow

Explore the API and browser-only merchant tools.

Create UPI checkout orders, verify signed events, or test the free calculators and generators without exposing credentials.