Skip to content

DocShare Deployment Guide

Complete guide for deploying DocShare in development and production environments.

Ready-to-use examples: See examples/ for Docker Compose and Helm configurations for common deployment scenarios.

Table of Contents

  1. Quick Start (Development)
  2. Development Setup
  3. Production Deployment
  4. AWS S3 Setup
  5. Environment Variables
  6. Database Management
  7. Backup & Recovery
  8. Monitoring
  9. Security Hardening
  10. Scaling
  11. Troubleshooting

Quick Start (Development)

The fastest way to get DocShare running locally:

# Clone repository
git clone <repository-url>
cd docshare

Choosing the Right Configuration

File Use Case When to Use
docker-compose.dev.yml Development Building/contributing to DocShare
docker-compose.yml Production Running pre-built release images
# Start all services (builds from local source)
docker-compose -f docker-compose.dev.yml up -d

# Access the application
# Frontend: http://localhost:3001
# Backend: http://localhost:8080

Production

# Start all services (uses pre-built images)
docker-compose up -d

# Access the application
# Frontend: http://localhost:3001
# Backend: http://localhost:8080

That's it! The application is now running with: - PostgreSQL database - AWS S3 object storage - Gotenberg document converter - Backend API server - Frontend web application

Note: You must have an AWS S3 bucket configured before starting the application. See the AWS S3 Setup section for details.

First-Time Setup

  1. Register the first user
  2. Navigate to http://localhost:3001/register
  3. Create an account
  4. First user is automatically assigned admin role

Development Setup

Prerequisites

Required: - Docker 20.10+ and Docker Compose 2.0+ - Git

Optional (for local development without Docker): - Go 1.24+ - Node.js 22+ - PostgreSQL 16+ - AWS account with S3 bucket

Option 1: Full Docker Development

Best for: Quick start, consistent environment, building from source

# Start all services (builds from local source)
docker-compose -f docker-compose.dev.yml up -d

# View logs
docker-compose -f docker-compose.dev.yml logs -f api
docker-compose -f docker-compose.dev.yml logs -f web

# Stop all services
docker-compose -f docker-compose.dev.yml down

# Stop and remove volumes (fresh start)
docker-compose -f docker-compose.dev.yml down -v

# Rebuild after code changes
docker-compose -f docker-compose.dev.yml up -d --build

Option 2: Hybrid Development

Best for: API or web development with live reload

API Development (Local)

# Start dependencies only
docker-compose up -d postgres gotenberg

# Set environment variables
export DB_HOST=localhost
export DB_PORT=5432
export DB_USER=docshare
export DB_PASSWORD=docshare_secret
export DB_NAME=docshare
export DB_SSLMODE=disable
export S3_REGION=us-east-1
export S3_BUCKET=docshare
export S3_ACCESS_KEY=<your-aws-access-key>
export S3_SECRET_KEY=<your-aws-secret-key>
export JWT_SECRET=dev-secret-change-in-production
export GOTENBERG_URL=http://localhost:3000
export SERVER_PORT=8080

# Run api
cd api
go run cmd/server/main.go

Web Development (Local)

# Start api services
docker-compose up -d postgres api gotenberg

# Build args (defaults to /api for reverse proxy, use explicit URL for direct api access)
export API_URL=http://localhost:8080/api
export WEB_URL=http://localhost:3001

# Install dependencies
cd web
npm install

# Run development server
API_URL=http://localhost:8080/api npm run dev

# Access at http://localhost:3001

Option 3: Fully Local Development

Best for: Offline development, custom configurations

1. Install Dependencies

PostgreSQL:

# macOS
brew install postgresql@16
brew services start postgresql@16

# Ubuntu
sudo apt install postgresql-16
sudo systemctl start postgresql

# Create database
createdb docshare

Gotenberg:

# Use Docker for Gotenberg (recommended)
docker run -d -p 3000:3000 gotenberg/gotenberg:8

2. Configure & Run

Follow the same environment variable setup as Option 2, then run api and web as described above.


Production Deployment

Architecture Overview

┌─────────────────────────────────────────────────────────┐
│                     Internet                            │
└──────────────────────┬──────────────────────────────────┘
                        │ HTTPS (443)
┌──────────────────────▼──────────────────────────────────┐
│              Reverse Proxy (Nginx/Traefik)              │
│  - TLS Termination                                      │
│  - Rate Limiting                                        │
│  - Static Asset Caching                                 │
└────────┬──────────────────────────┬─────────────────────┘
          │                          │
          │ HTTP (3001)              │ HTTP (8080)
          │                          │
┌────────▼────────┐       ┌─────────▼────────┐
│   Frontend      │       │     Backend      │
│   Container     │       │    Container     │
│   (Next.js)     │       │      (Go)        │
└─────────────────┘       └─────────┬────────┘
                    ┌────────────────┼──────────────┐
                    │                │              │
            ┌───────▼────────┐ ┌─────▼─────┐ ┌──────▼──────┐
            │   PostgreSQL   │ │  AWS S3   │ │  Gotenberg  │
            │  (Primary DB)  │ │ (Storage) │ │  (Convert)  │
            └────────────────┘ └───────────┘ └─────────────┘

Deployment Options

Option 1: Docker Compose (Small to Medium Scale)

Best for: Single server deployments, < 1000 users

Steps:

  1. Prepare server

    # Update system
    sudo apt update && sudo apt upgrade -y
    
    # Install Docker
    curl -fsSL https://get.docker.com -o get-docker.sh
    sudo sh get-docker.sh
    
    # Install Docker Compose
    sudo apt install docker-compose-plugin
    

  2. Clone and configure

    git clone <repository-url>
    cd docshare
    
    # Create production environment file
    cp .env.example .env
    nano .env  # Edit configuration (see Environment Variables section)
    

  3. Update docker-compose.yml for production

    # docker-compose.prod.yml
    services:
      postgres:
        restart: always
        environment:
          POSTGRES_PASSWORD: ${DB_PASSWORD}  # Use strong password
    
      api:
        restart: always
        environment:
          JWT_SECRET: ${JWT_SECRET}  # Use long random string (32+ chars)
          DB_PASSWORD: ${DB_PASSWORD}
          S3_ACCESS_KEY: ${S3_ACCESS_KEY}
          S3_SECRET_KEY: ${S3_SECRET_KEY}
    
      web:
        restart: always
    

  4. Setup reverse proxy (Nginx)

    # /etc/nginx/sites-available/docshare
    server {
        listen 80;
        server_name your-domain.com;
        return 301 https://$server_name$request_uri;
    }
    
    server {
        listen 443 ssl http2;
        server_name your-domain.com;
    
        ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
    
        # Frontend
        location / {
            proxy_pass http://localhost:3001;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_cache_bypass $http_upgrade;
        }
    
        # Backend API
        location /api {
            proxy_pass http://localhost:8080;
            proxy_http_version 1.1;
            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;
    
            # File upload size limit
            client_max_body_size 100M;
        }
    
        # Rate limiting
        limit_req_zone $binary_remote_addr zone=api:10m rate=100r/m;
        location /api/auth {
            limit_req zone=api burst=5;
            proxy_pass http://localhost:8080;
        }
    }
    

  5. Setup SSL with Let's Encrypt

    sudo apt install certbot python3-certbot-nginx
    sudo certbot --nginx -d your-domain.com
    

  6. Deploy

    docker-compose up -d
    

Or with explicit file:

docker-compose -f docker-compose.yml up -d

Option 2: Kubernetes with Helm (Large Scale)

Best for: Multi-server deployments, high availability, > 10,000 users

DocShare provides an official Helm chart for Kubernetes deployments. Install from the OCI registry:

helm install docshare oci://ghcr.io/hayward-solutions/charts/docshare \
  --namespace docshare \
  --create-namespace \
  -f production-values.yaml

The chart includes: - Deployments for api, web, and Gotenberg - Bundled PostgreSQL via Bitnami subchart (or bring your own) - Ingress with TLS support - Configurable replicas, resources, and environment - Secret management (auto-generated or existing secrets)

See the Helm Chart documentation for the full configuration reference, production examples, and external database/storage setup.

Option 3: AWS CloudFormation (ECS Fargate)

Best for: AWS-native deployments, managed infrastructure, > 100 users

DocShare provides a CloudFormation template for deploying to AWS ECS Fargate with:

  • ECS Fargate - Serverless container orchestration
  • Application Load Balancer - HTTPS termination, path-based routing
  • RDS PostgreSQL - Managed database with encryption at rest
  • S3 - Object storage with KMS encryption
  • Auto Scaling - CPU-based scaling policies
# Deploy to AWS
aws cloudformation create-stack \
  --stack-name docshare \
  --template-body file://examples/cloudformation/docshare.yaml \
  --parameters \
      ParameterKey=DomainName,ParameterValue=docshare.example.com \
      ParameterKey=Route53HostedZoneId,ParameterValue=Z1234567890ABC \
      ParameterKey=VpcId,ParameterValue=vpc-abc123 \
      ParameterKey=PublicSubnetIds,ParameterValue="subnet-aaa,subnet-bbb" \
      ParameterKey=PrivateSubnetIds,ParameterValue="subnet-xxx,subnet-yyy" \
      ParameterKey=JwtSecret,ParameterValue=$(openssl rand -hex 32) \
  --capabilities CAPABILITY_IAM

See the CloudFormation documentation for detailed configuration, cost estimates, and troubleshooting.

Option 4: Managed Services (Hybrid Cloud)

Best for: Minimal infrastructure management

Architecture: - Frontend: Vercel, Netlify, or Cloudflare Pages - Backend: AWS ECS, Google Cloud Run, or DigitalOcean App Platform - Database: AWS RDS, Google Cloud SQL, or DigitalOcean Managed Database - Storage: AWS S3, Google Cloud Storage, or DigitalOcean Spaces - Document Conversion: Container on Cloud Run or ECS


AWS S3 Setup

DocShare uses AWS S3 for object storage. This section covers the required setup.

Bucket Creation

  1. Create an S3 bucket:

    aws s3 mb s3://docshare-prod --region us-east-1
    

  2. Configure bucket for security:

    # Block public access (recommended)
    aws s3api put-public-access-block \
      --bucket docshare-prod \
      --public-access-block-configuration "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"
    
    # Enable versioning (optional, for recovery)
    aws s3api put-bucket-versioning \
      --bucket docshare-prod \
      --versioning-configuration Status=Enabled
    
    # Enable encryption
    aws s3api put-bucket-encryption \
      --bucket docshare-prod \
      --server-side-encryption-configuration '{"Rules":[{"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"AES256"}}]}'
    

  3. Configure lifecycle policy (optional):

    {
      "Rules": [
        {
          "ID": "DeleteOldVersions",
          "Status": "Enabled",
          "NoncurrentVersionExpiration": {
            "NoncurrentDays": 30
          }
        }
      ]
    }
    
    aws s3api put-bucket-lifecycle-configuration \
      --bucket docshare-prod \
      --lifecycle-configuration file://lifecycle.json
    

IAM User Authentication (Development/Standalone)

For non-EKS deployments, use IAM user credentials:

  1. Create IAM user:

    aws iam create-user --user-name docshare-app
    

  2. Create access key:

    aws iam create-access-key --user-name docshare-app
    

  3. Attach policy:

    aws iam attach-user-policy \
      --user-name docshare-app \
      --policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess
    

IAM Role Authentication (EKS/Production)

For EKS deployments, use IAM roles for service accounts (IRSA):

  1. Create IAM OIDC provider (if not exists):

    eksctl utils associate-iam-oidc-provider --cluster your-cluster --approve
    

  2. Create IAM policy:

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "DocShareS3Access",
          "Effect": "Allow",
          "Action": [
            "s3:PutObject",
            "s3:GetObject",
            "s3:DeleteObject",
            "s3:ListBucket",
            "s3:GetBucketLocation"
          ],
          "Resource": [
            "arn:aws:s3:::docshare-prod",
            "arn:aws:s3:::docshare-prod/*"
          ]
        }
      ]
    }
    
    aws iam create-policy \
      --policy-name DocShareS3Access \
      --policy-document file://s3-policy.json
    

  3. Create IAM role for service account:

    eksctl create iamserviceaccount \
      --name docshare-api \
      --namespace docshare \
      --cluster your-cluster \
      --attach-policy-arn arn:aws:iam::<account-id>:policy/DocShareS3Access \
      --approve \
      --override-existing-serviceaccounts
    

  4. Configure environment (no access keys needed): When using IAM roles, leave S3_ACCESS_KEY and S3_SECRET_KEY empty or unset. The SDK will automatically use the IAM role.


Environment Variables

Backend Environment Variables

Variable Required Default Description
DB_HOST Yes localhost PostgreSQL host
DB_PORT Yes 5432 PostgreSQL port
DB_USER Yes docshare PostgreSQL username
DB_PASSWORD Yes docshare_secret PostgreSQL password
DB_NAME Yes docshare PostgreSQL database name
DB_SSLMODE Yes disable PostgreSQL SSL mode (disable, require, verify-full)
S3_REGION Yes us-east-1 AWS region for S3 bucket
S3_ENDPOINT No Auto-derived from region S3 endpoint (internal), defaults to s3.$REGION.amazonaws.com
S3_PUBLIC_ENDPOINT No Same as S3_ENDPOINT S3 endpoint (public, for presigned URLs)
S3_ACCESS_KEY No (empty) AWS access key (empty = use IAM role)
S3_SECRET_KEY No (empty) AWS secret key (empty = use IAM role)
S3_BUCKET Yes docshare S3 bucket name
S3_USE_SSL Yes true Use SSL for S3 connection
JWT_SECRET Yes change-me-in-production JWT signing secret (32+ characters)
JWT_EXPIRATION_HOURS No 24 JWT token lifetime in hours
GOTENBERG_URL Yes http://localhost:3000 Gotenberg service URL
SERVER_PORT No 8080 Backend server port
WEB_URL No http://localhost:3001 Frontend URL for CORS and device flow
API_URL No http://localhost:8080/api Backend API URL (include /api path). Auto-derives OAuth redirect URLs if not set
AUDIT_EXPORT_INTERVAL No 1h Interval for exporting audit logs to S3 (Go duration format, e.g. 30m, 2h)

Frontend Environment Variables

The web uses API_URL (mapped to NEXT_PUBLIC_API_URL at build time) to construct API calls. Set these at build time:

Variable Required Default Description
API_URL No /api Backend API URL (include /api path). Defaults to /api for reverse proxy setups.
WEB_URL No (empty) Frontend URL for redirects

For local development, pass as build args:

docker build --build-arg API_URL=http://localhost:8080/api --build-arg WEB_URL=http://localhost:3001

Or set in docker-compose:

web:
  build:
    args:
      API_URL: http://localhost:8080/api
      WEB_URL: http://localhost:3001

The default /api works when the web is served behind a reverse proxy that routes /api to the api.

Production Environment Variable Recommendations

# Backend (.env.api.prod)
DB_HOST=postgres-prod.example.com
DB_PORT=5432
DB_USER=docshare_prod
DB_PASSWORD=<generate-strong-password>  # Use password manager
DB_NAME=docshare_production
DB_SSLMODE=require

S3_REGION=us-east-1
S3_BUCKET=docshare-prod
S3_ACCESS_KEY=<aws-access-key>  # Or leave empty for IAM role
S3_SECRET_KEY=<aws-secret-key>  # Or leave empty for IAM role

JWT_SECRET=<generate-random-64-char-string>  # openssl rand -hex 32
JWT_EXPIRATION_HOURS=24

GOTENBERG_URL=http://gotenberg:3000
SERVER_PORT=8080
WEB_URL=https://docshare.example.com
API_URL=https://docshare.example.com/api
AUDIT_EXPORT_INTERVAL=1h
# Frontend (.env.web.prod)
# Build with API_URL set to your API base URL
ARG API_URL=https://docshare.example.com/api
ARG WEB_URL=https://docshare.example.com

Generating Secrets

# JWT Secret (64 characters)
openssl rand -hex 32

# Database Password (32 characters)
openssl rand -base64 32

# AWS credentials - use IAM roles in production
# For development, create access keys in AWS IAM console

Database Management

Migrations

DocShare uses GORM's AutoMigrate feature. Database schema is automatically created/updated on application startup.

Models migrated: - Users - Files - Groups - GroupMemberships - Shares - AuditLogs - AuditExportCursors - Activities

Manual Migration (If needed)

If you need to run migrations manually or create custom migrations:

# Connect to PostgreSQL
psql -h localhost -U docshare -d docshare

# Example: Add index
CREATE INDEX idx_files_created_at ON files(created_at);

# Example: Add column
ALTER TABLE users ADD COLUMN phone_number VARCHAR(20);

Database Backup

Automated daily backups:

#!/bin/bash
# /usr/local/bin/backup-docshare.sh

BACKUP_DIR="/backups/docshare"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/docshare_$DATE.sql.gz"

# PostgreSQL backup
docker exec docshare-postgres pg_dump -U docshare docshare | gzip > $BACKUP_FILE

# Retain only last 7 days
find $BACKUP_DIR -name "*.sql.gz" -mtime +7 -delete

echo "Backup completed: $BACKUP_FILE"

Setup cron job:

# Run daily at 2 AM
crontab -e
0 2 * * * /usr/local/bin/backup-docshare.sh >> /var/log/docshare-backup.log 2>&1

Database Restore

# Stop application
docker-compose down api

# Restore database
gunzip -c /backups/docshare/docshare_20240211_020000.sql.gz | \
  docker exec -i docshare-postgres psql -U docshare docshare

# Restart application
docker-compose up -d api

Database Maintenance

Regular maintenance tasks:

-- Vacuum (reclaim storage)
VACUUM ANALYZE;

-- Reindex (rebuild indexes)
REINDEX DATABASE docshare;

-- Check database size
SELECT pg_size_pretty(pg_database_size('docshare'));

-- Check table sizes
SELECT 
  schemaname,
  tablename,
  pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS size
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC;

-- Clean up expired shares
DELETE FROM shares WHERE expires_at < NOW();

Automate with cron:

# Weekly maintenance on Sundays at 3 AM
0 3 * * 0 docker exec docshare-postgres psql -U docshare -d docshare -c "VACUUM ANALYZE;" >> /var/log/docshare-maintenance.log 2>&1


Backup & Recovery

Full Backup Strategy

What to backup: 1. PostgreSQL database (metadata) 2. S3 bucket versioning (enabled for file recovery) 3. Environment configuration files 4. SSL certificates

Note: S3 handles file storage backup through versioning and cross-region replication. No manual backup needed for file content.

Backup script:

#!/bin/bash
# /usr/local/bin/full-backup-docshare.sh

BACKUP_ROOT="/backups/docshare"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="$BACKUP_ROOT/$DATE"

mkdir -p $BACKUP_DIR

echo "Starting full backup: $DATE"

# 1. PostgreSQL
echo "Backing up database..."
docker exec docshare-postgres pg_dump -U docshare docshare | \
  gzip > $BACKUP_DIR/database.sql.gz

# 2. Configuration
echo "Backing up configuration..."
cp .env $BACKUP_DIR/
cp docker-compose.yml $BACKUP_DIR/

# 3. SSL certificates
echo "Backing up SSL certificates..."
if [ -d "/etc/letsencrypt" ]; then
  sudo tar czf $BACKUP_DIR/ssl-certs.tar.gz /etc/letsencrypt
fi

# Create manifest
cat > $BACKUP_DIR/manifest.txt << EOF
Backup Date: $DATE
Database: database.sql.gz
Configuration: .env, docker-compose.yml
SSL Certificates: ssl-certs.tar.gz
EOF

# Compress entire backup
cd $BACKUP_ROOT
tar czf "docshare_full_$DATE.tar.gz" $DATE
rm -rf $DATE

echo "Backup completed: docshare_full_$DATE.tar.gz"

# Optional: Upload to remote storage
# aws s3 cp "docshare_full_$DATE.tar.gz" s3://your-backup-bucket/

Disaster Recovery Procedure

Scenario: Complete server failure

  1. Provision new server

    # Install Docker & Docker Compose
    curl -fsSL https://get.docker.com -o get-docker.sh
    sudo sh get-docker.sh
    

  2. Restore backup

    # Download backup (if stored remotely)
    aws s3 cp s3://your-backup-bucket/docshare_full_20240211_020000.tar.gz .
    
    # Extract backup
    tar xzf docshare_full_20240211_020000.tar.gz
    cd 20240211_020000
    
    # Restore configuration
    cp .env ../
    cp docker-compose.yml ../
    

  3. Start infrastructure services

    cd ..
    docker-compose up -d postgres
    
    # Wait for services to be ready
    sleep 10
    

  4. Restore database bash gunzip -c 20240211_020000/database.sql.gz | \ docker exec -i docshare-postgres psql -U docshare docshare

  5. Restore SSL certificates bash sudo tar xzf 20240211_020000/ssl-certs.tar.gz -C /

  6. Start application bash docker-compose up -d

  7. Verify

    # Check all services are running
    docker-compose ps
    
    # Check logs
    docker-compose logs -f api
    
    # Test login
    curl https://your-domain.com/api/health
    

Expected recovery time: 30-60 minutes


Monitoring

Health Checks

Backend health endpoint:

curl http://localhost:8080/health
# Expected: {"status":"ok"}

Container health:

docker-compose ps
# All containers should show "Up" and "healthy"

Logging

View logs:

# All services
docker-compose logs -f

# Specific service
docker-compose logs -f api
docker-compose logs -f web

# Last 100 lines
docker-compose logs --tail=100 api

# With timestamps
docker-compose logs -f -t api

Centralized logging (production):

Use a log aggregation solution: - ELK Stack (Elasticsearch, Logstash, Kibana) - Loki + Grafana - Datadog - CloudWatch Logs (AWS)

Example: Ship logs to Loki

# docker-compose.yml
services:
  api:
    logging:
      driver: loki
      options:
        loki-url: "http://loki:3100/loki/api/v1/push"

Metrics

Key metrics to monitor:

  1. Application Metrics:
  2. Request rate (requests/sec)
  3. Response time (p50, p95, p99)
  4. Error rate (4xx, 5xx)
  5. Active users

  6. System Metrics:

  7. CPU usage
  8. Memory usage
  9. Disk usage
  10. Network I/O

  11. Database Metrics:

  12. Connection count
  13. Query performance
  14. Slow queries
  15. Database size

  16. Storage Metrics:

  17. Object count
  18. Storage usage
  19. Upload/download rate

Prometheus + Grafana setup:

# docker-compose.monitoring.yml
services:
  prometheus:
    image: prom/prometheus
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    ports:
      - "9090:9090"

  grafana:
    image: grafana/grafana
    volumes:
      - grafana_data:/var/lib/grafana
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin

volumes:
  prometheus_data:
  grafana_data:

Alerts

Critical alerts to configure:

  1. Service down (any container stopped)
  2. High error rate (>5% 5xx responses)
  3. High response time (p95 > 2 seconds)
  4. Database connection failures
  5. Disk usage > 80%
  6. Memory usage > 90%

Example: Alertmanager configuration

# alertmanager.yml
route:
  receiver: 'email'
  group_by: ['alertname']
  group_wait: 10s
  group_interval: 10s
  repeat_interval: 1h

receivers:
  - name: 'email'
    email_configs:
      - to: 'ops@example.com'
        from: 'alerts@example.com'
        smarthost: 'smtp.gmail.com:587'
        auth_username: 'alerts@example.com'
        auth_password: 'password'


Security Hardening

1. Change Default Credentials

CRITICAL: Before going to production

  • Change PostgreSQL password
  • Configure AWS IAM roles or secure access keys for S3
  • Generate strong JWT secret (32+ characters)
  • Update admin user password

2. Network Security

Firewall rules:

# Allow only necessary ports
sudo ufw allow 22    # SSH
sudo ufw allow 80    # HTTP
sudo ufw allow 443   # HTTPS
sudo ufw enable

# Block direct access to internal services
sudo ufw deny 5432   # PostgreSQL
sudo ufw deny 8080   # Backend (use reverse proxy)

Docker network isolation:

# docker-compose.yml
services:
  api:
    networks:
      - web
      - api

  postgres:
    networks:
      - api  # Not accessible from web network

  web:
    networks:
      - web

networks:
  web:
  api:
    internal: true  # No external access

3. SSL/TLS Configuration

Strong SSL configuration (Nginx):

ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
ssl_prefer_server_ciphers off;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_stapling on;
ssl_stapling_verify on;
add_header Strict-Transport-Security "max-age=63072000" always;

4. Database Security

PostgreSQL hardening:

-- Revoke public schema access
REVOKE ALL ON SCHEMA public FROM PUBLIC;

-- Create read-only user for backups
CREATE USER docshare_backup WITH PASSWORD 'strong-password';
GRANT CONNECT ON DATABASE docshare TO docshare_backup;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO docshare_backup;

-- Limit connections
ALTER DATABASE docshare CONNECTION LIMIT 100;

5. Application Security

Content Security Policy (CSP):

add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https://api.your-domain.com";

Additional headers:

add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;

6. Rate Limiting

Nginx rate limiting:

# Define rate limit zones
limit_req_zone $binary_remote_addr zone=auth:10m rate=5r/m;
limit_req_zone $binary_remote_addr zone=api:10m rate=100r/m;
limit_req_zone $binary_remote_addr zone=upload:10m rate=10r/m;

server {
    # Auth endpoints (strict)
    location /api/auth {
        limit_req zone=auth burst=5 nodelay;
        proxy_pass http://api;
    }

    # Upload endpoints
    location /api/files/upload {
        limit_req zone=upload burst=3 nodelay;
        proxy_pass http://api;
    }

    # General API
    location /api {
        limit_req zone=api burst=20 nodelay;
        proxy_pass http://api;
    }
}

7. Regular Updates

Automated security updates (Ubuntu):

sudo apt install unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades

Update containers regularly:

# Pull latest images
docker-compose pull

# Recreate containers
docker-compose up -d

# Remove old images
docker image prune -a

8. Distroless Container Images

DocShare uses Google's distroless container images for production deployments, providing:

  • Reduced attack surface — No shell, package manager, or unnecessary utilities
  • Smaller image sizes — ~50-70% smaller than Alpine-based images
  • Fewer CVEs — Minimal packages mean fewer vulnerabilities to patch

Runtime images used:

Service Distroless Image
Backend (Go) gcr.io/distroless/static-debian12
Frontend (Node.js) gcr.io/distroless/nodejs22-debian12

Debugging distroless containers:

For debugging, use the :debug variants:

# Development docker-compose.yml
services:
  api:
    image: gcr.io/distroless/static-debian12:debug

# Then you can exec into the container
docker exec -it docshare-api sh

Note: Distroless images run as non-root by default. If you need root access for debugging, use the debug variants.


Scaling

Vertical Scaling

Increase resources for existing containers:

# docker-compose.yml
services:
  api:
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 2G
        reservations:
          cpus: '1'
          memory: 1G

  postgres:
    deploy:
      resources:
        limits:
          cpus: '4'
          memory: 8G

Horizontal Scaling

1. Load Balanced Backend

Nginx upstream configuration:

upstream api {
    least_conn;
    server api1:8080;
    server api2:8080;
    server api3:8080;
}

server {
    location /api {
        proxy_pass http://api;
    }
}

Docker Compose with replicas:

services:
  api:
    image: docshare-api
    deploy:
      replicas: 3
    environment:
      # ... same environment variables

2. Database Read Replicas

PostgreSQL replication:

services:
  postgres-primary:
    image: postgres:16-alpine
    environment:
      POSTGRES_REPLICATION_MODE: master
      POSTGRES_REPLICATION_USER: replicator
      POSTGRES_REPLICATION_PASSWORD: replication_pass

  postgres-replica:
    image: postgres:16-alpine
    environment:
      POSTGRES_REPLICATION_MODE: slave
      POSTGRES_MASTER_HOST: postgres-primary
      POSTGRES_REPLICATION_USER: replicator
      POSTGRES_REPLICATION_PASSWORD: replication_pass

Configure api to use read replicas:

// Separate read/write connections
dbWrite := ConnectPostgres(primaryHost)
dbRead := ConnectPostgres(replicaHost)

// Use read connection for queries
files, err := dbRead.Find(&files)

// Use write connection for mutations
err := dbWrite.Create(&file)

3. Distributed Storage

AWS S3 provides built-in durability and availability: - 99.999999999% (11 9s) durability - Cross-region replication for disaster recovery - S3 Transfer Acceleration for faster uploads - No additional infrastructure needed

Caching

Add Redis for caching:

services:
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data

Cache strategies: 1. User data cache - Cache user objects after login 2. Permission cache - Cache expensive permission checks 3. File metadata cache - Cache frequently accessed file details 4. Session cache - Store JWT tokens with expiration


Troubleshooting

Common Issues

1. Container won't start

Symptom: docker-compose ps shows container as "Restarting" or "Exit 1"

Solution:

# Check logs
docker-compose logs api

# Common causes:
# - Environment variable missing
# - Database connection failed
# - Port already in use

# Check port conflicts
sudo lsof -i :8080

2. Cannot connect to database

Symptom: Backend logs show "connection refused" or "password authentication failed"

Solution:

# Verify PostgreSQL is running
docker-compose ps postgres

# Check PostgreSQL logs
docker-compose logs postgres

# Test connection manually
docker exec -it docshare-postgres psql -U docshare -d docshare

# Verify environment variables match
docker-compose exec api env | grep DB_

3. File upload fails

Symptom: 400 or 500 error on upload, or upload hangs

Solution:

# Verify S3 bucket exists
aws s3 ls s3://docshare-prod

# Check IAM permissions
aws sts get-caller-identity

# Check api can reach S3
docker-compose exec api curl https://s3.amazonaws.com

# Check file size limit (api)
# Default: 100MB in cmd/server/main.go

# Check Nginx file size limit
# client_max_body_size 100M;

4. Preview generation fails

Symptom: Office documents don't show preview

Solution:

# Check Gotenberg is running
docker-compose ps gotenberg

# Test Gotenberg manually
curl -X POST http://localhost:3000/forms/libreoffice/convert \
  -F file=@test.docx \
  -o test.pdf

# Check api can reach Gotenberg
docker-compose exec api curl http://gotenberg:3000/health

5. High memory usage

Symptom: System running slow, OOM errors

Solution:

# Check container memory usage
docker stats

# Identify memory hog
docker stats --no-stream --format "table {{.Container}}\t{{.MemUsage}}"

# Common causes:
# - Too many concurrent uploads
# - Large preview generations
# - Database query without limit

# Add memory limits
# See docker-compose.yml deploy.resources section

6. Slow queries

Symptom: API responses are slow, database CPU high

Solution:

-- Find slow queries
SELECT 
  pid,
  now() - query_start AS duration,
  query
FROM pg_stat_activity
WHERE state = 'active'
ORDER BY duration DESC;

-- Check missing indexes
SELECT 
  schemaname,
  tablename,
  attname,
  n_distinct,
  correlation
FROM pg_stats
WHERE schemaname = 'public'
ORDER BY abs(correlation) ASC;

-- Add missing indexes
CREATE INDEX idx_shares_file_id_permission ON shares(file_id, permission);

7. SSL certificate issues

Symptom: "Certificate has expired" or "Certificate not trusted"

Solution:

# Check certificate expiration
sudo certbot certificates

# Renew certificate
sudo certbot renew

# Test auto-renewal
sudo certbot renew --dry-run

# Force renewal
sudo certbot renew --force-renewal

# Reload Nginx
sudo systemctl reload nginx

Debug Mode

Enable verbose logging:

# Backend (Go)
# Set log level to debug in pkg/logger/logger.go

# Frontend (Next.js)
export DEBUG=*
npm run dev

# PostgreSQL
# Edit postgresql.conf
log_statement = 'all'
log_duration = on

Performance Profiling

Backend profiling:

# Add pprof to main.go
import _ "net/http/pprof"

# Start profiling server
go tool pprof http://localhost:6060/debug/pprof/profile

# Memory profiling
go tool pprof http://localhost:6060/debug/pprof/heap

Database profiling:

-- Enable query timing
\timing

-- Analyze query plan
EXPLAIN ANALYZE SELECT * FROM files WHERE owner_id = 'uuid';

-- Check index usage
SELECT * FROM pg_stat_user_indexes;

Getting Help

  1. Check logs first - Most issues are visible in logs
  2. Search issues - Check GitHub issues for similar problems
  3. Ask community - Join Discord/Slack for help
  4. Create issue - Provide logs, configuration, and reproduction steps

Additional Resources

  • README.md - Project overview
  • ARCHITECTURE.md - Architecture details
  • API.md - API documentation
  • HELM.md - Helm chart reference
  • CLOUDFORMATION.md - AWS CloudFormation deployment
  • Docker Documentation: https://docs.docker.com/
  • PostgreSQL Documentation: https://www.postgresql.org/docs/
  • AWS S3 Documentation: https://docs.aws.amazon.com/s3/
  • AWS ECS Documentation: https://docs.aws.amazon.com/ecs/
  • Nginx Documentation: https://nginx.org/en/docs/

Last Updated: February 2026