Skip to main content

Self-Hosted Deployment

Deploy DotVault on your own infrastructure for complete control over your secrets management.

Overview

Self-hosted deployment provides:

  • Complete data control: All data stays in your infrastructure
  • Custom security policies: Implement your own security controls
  • Compliance: Meet strict regulatory requirements
  • No vendor lock-in: Own your deployment
  • Cost control: Predictable infrastructure costs

Deployment Options

Simplest deployment for small to medium teams.

Requirements:

  • Docker 20.10+
  • Docker Compose 2.0+
  • 2GB RAM minimum
  • 10GB storage

Quick Start:

# 1. Clone the repository
git clone https://github.com/dotvault/dotvault.git
cd dotvault

# 2. Create environment file
cp .env.example .env

# 3. Edit .env with your settings
nano .env

# 4. Start services
docker-compose up -d

# 5. Run migrations
docker-compose exec app npm run db:migrate

# 6. Create admin user
docker-compose exec app npm run create-admin

# 7. Access DotVault
open http://localhost:3000

docker-compose.yml:

version: "3.8"

services:
app:
image: dotvault/dotvault:latest
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgresql://dotvault:${DB_PASSWORD}@db:5432/dotvault
- REDIS_URL=redis://redis:6379
- BETTER_AUTH_SECRET=${AUTH_SECRET}
- ENCRYPTION_KEY=${ENCRYPTION_KEY}
- NEXT_PUBLIC_APP_URL=${APP_URL}
- SMTP_HOST=${SMTP_HOST}
- SMTP_PORT=${SMTP_PORT}
- SMTP_USER=${SMTP_USER}
- SMTP_PASS=${SMTP_PASS}
- SMTP_FROM=${SMTP_FROM}
depends_on:
- db
- redis
volumes:
- app-data:/app/data
restart: unless-stopped

db:
image: postgres:16-alpine
environment:
- POSTGRES_USER=dotvault
- POSTGRES_PASSWORD=${DB_PASSWORD}
- POSTGRES_DB=dotvault
volumes:
- postgres-data:/var/lib/postgresql/data
restart: unless-stopped

redis:
image: redis:7-alpine
volumes:
- redis-data:/data
restart: unless-stopped

volumes:
app-data:
postgres-data:
redis-data:

Kubernetes

Production-grade deployment with high availability.

Requirements:

  • Kubernetes 1.25+
  • Helm 3.0+
  • Ingress controller
  • Cert-manager (for TLS)

Helm Installation:

# Add DotVault Helm repository
helm repo add dotvault https://charts.dotvault.io
helm repo update

# Create namespace
kubectl create namespace dotvault

# Create secrets
kubectl create secret generic dotvault-secrets \
--from-literal=database-url='postgresql://...' \
--from-literal=auth-secret='...' \
--from-literal=encryption-key='...' \
-n dotvault

# Install DotVault
helm install dotvault dotvault/dotvault \
--namespace dotvault \
--set ingress.enabled=true \
--set ingress.host=dotvault.company.com

# Check status
kubectl get pods -n dotvault

Manual Kubernetes Deployment:

See k8s/ directory in repository for:

  • Deployment manifests
  • Service definitions
  • ConfigMaps
  • Secrets templates
  • Ingress examples

AWS ECS

Deploy on AWS Elastic Container Service.

CloudFormation Template:

AWSTemplateFormatVersion: "2010-09-09"
Description: DotVault on ECS

Resources:
DotVaultCluster:
Type: AWS::ECS::Cluster
Properties:
ClusterName: dotvault

DotVaultService:
Type: AWS::ECS::Service
Properties:
ServiceName: dotvault
Cluster: !Ref DotVaultCluster
TaskDefinition: !Ref DotVaultTask
DesiredCount: 2
LaunchType: FARGATE
NetworkConfiguration:
AwsvpcConfiguration:
Subnets: !Ref PrivateSubnets
SecurityGroups: [!Ref DotVaultSecurityGroup]

DotVaultTask:
Type: AWS::ECS::TaskDefinition
Properties:
Family: dotvault
NetworkMode: awsvpc
RequiresCompatibilities: [FARGATE]
Cpu: 512
Memory: 1024
ContainerDefinitions:
- Name: dotvault
Image: dotvault/dotvault:latest
PortMappings:
- ContainerPort: 3000
Environment:
- Name: DATABASE_URL
Value: !Sub "postgresql://${DBUser}:${DBPassword}@${DBEndpoint}/dotvault"
Secrets:
- Name: ENCRYPTION_KEY
ValueFrom: !Sub "${EncryptionKeySecret}:encryption-key"

Google Cloud Run

Serverless deployment on GCP.

Deployment:

# Build container
gcloud builds submit --tag gcr.io/PROJECT_ID/dotvault

# Deploy to Cloud Run
gcloud run deploy dotvault \
--image gcr.io/PROJECT_ID/dotvault \
--platform managed \
--region us-central1 \
--allow-unauthenticated \
--set-env-vars "DATABASE_URL=postgresql://..." \
--set-secrets "ENCRYPTION_KEY=encryption-key:latest"

# Connect to Cloud SQL
gcloud run services update dotvault \
--add-cloudsql-instances PROJECT_ID:REGION:INSTANCE_ID

Azure Container Instances

Deploy on Microsoft Azure.

Azure CLI:

# Create resource group
az group create --name dotvault --location eastus

# Create container
az container create \
--resource-group dotvault \
--name dotvault \
--image dotvault/dotvault:latest \
--ports 3000 \
--environment-variables DATABASE_URL='postgresql://...' \
--secrets ENCRYPTION_KEY='...' \
--dns-name-label dotvault

Configuration

Required Environment Variables

VariableDescriptionExample
DATABASE_URLPostgreSQL connection stringpostgresql://user:pass@host:5432/db
BETTER_AUTH_SECRETSession signing secretGenerate with openssl rand -base64 32
ENCRYPTION_KEYAES-256 encryption keyGenerate with openssl rand -hex 32
NEXT_PUBLIC_APP_URLPublic URL of deploymenthttps://dotvault.company.com

Optional Environment Variables

VariableDescriptionDefault
REDIS_URLRedis connection stringredis://localhost:6379
SMTP_HOSTSMTP server for emails-
SMTP_PORTSMTP port587
SMTP_USERSMTP username-
SMTP_PASSSMTP password-
SMTP_FROMFrom email addressnoreply@dotvault.io
LOG_LEVELLogging levelinfo
MAX_UPLOAD_SIZEMax file upload size10mb
RATE_LIMIT_REQUESTSAPI rate limit100
RATE_LIMIT_WINDOWRate limit window60

Database Setup

PostgreSQL Requirements:

  • Version 14 or higher
  • SSL enabled (recommended)
  • Regular backups configured
  • Connection pooling (PgBouncer recommended)

Migration:

# Run migrations
docker-compose exec app npm run db:migrate

# Check status
docker-compose exec app npm run db:status

# Rollback (if needed)
docker-compose exec app npm run db:rollback

SSL/TLS

Let's Encrypt (Recommended):

# docker-compose.yml with Traefik
services:
traefik:
image: traefik:v2.10
command:
- "--api.insecure=true"
- "--providers.docker=true"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
- "--certificatesresolvers.letsencrypt.acme.email=admin@company.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
ports:
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./letsencrypt:/letsencrypt

app:
image: dotvault/dotvault:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.dotvault.rule=Host(`dotvault.company.com`)"
- "traefik.http.routers.dotvault.tls.certresolver=letsencrypt"

Custom Certificate:

# Mount certificate
docker run -v /path/to/cert.pem:/app/cert.pem \
-v /path/to/key.pem:/app/key.pem \
-e SSL_CERT=/app/cert.pem \
-e SSL_KEY=/app/key.pem \
dotvault/dotvault:latest

Backup and Recovery

Database Backups

Automated Backups:

# Daily backup script
#!/bin/bash
DATE=$(date +%Y%m%d_%H%M%S)
docker-compose exec -T db pg_dump -U dotvault dotvault > backup_$DATE.sql

# Upload to S3
aws s3 cp backup_$DATE.sql s3://my-backups/dotvault/

# Keep only last 30 days
aws s3 ls s3://my-backups/dotvault/ | \
awk '$1 < "'$(date -d '30 days ago' +%Y-%m-%d)'" {print $4}' | \
xargs -I {} aws s3 rm s3://my-backups/dotvault/{}

Point-in-Time Recovery:

# Enable WAL archiving in postgresql.conf
wal_level = replica
archive_mode = on
archive_command = 'cp %p /archive/%f'

# Continuous backup
pg_basebackup -D /backup -Fp -Xs -P -v

Encryption Key Backup

Critical: Backup encryption keys securely:

# Generate backup
echo $ENCRYPTION_KEY > encryption-key-backup.txt

# Encrypt backup
gpg --symmetric --cipher-algo AES256 encryption-key-backup.txt

# Store in multiple locations
# - Password manager
# - Hardware security module
# - Offline storage
# - Cloud KMS (AWS KMS, Google Cloud KMS, Azure Key Vault)

Disaster Recovery

Recovery Procedure:

  1. Restore Database:

    # From backup
    psql -U dotvault -d dotvault < backup_20240115.sql

    # Or from WAL archives
    pg_restore -U dotvault -d dotvault /backup/base.tar
  2. Verify Encryption Key:

    # Test key works
    docker-compose exec app npm run verify-key
  3. Start Services:

    docker-compose up -d
  4. Verify Functionality:

    • Test login
    • Verify secret access
    • Check audit logs

Security Hardening

Network Security

Firewall Rules:

# Allow only necessary ports
ufw allow 443/tcp
ufw allow 22/tcp
ufw deny 3000/tcp # Internal only
ufw enable

VPN Access:

Restrict admin access to VPN:

# docker-compose.yml
services:
app:
networks:
- internal
- vpn-only

networks:
vpn-only:
driver: bridge
internal: true
ipam:
config:
- subnet: 10.200.0.0/16

Database Security

SSL Configuration:

# postgresql.conf
ssl = on
ssl_cert_file = '/etc/ssl/certs/server.crt'
ssl_key_file = '/etc/ssl/private/server.key'
ssl_ca_file = '/etc/ssl/certs/ca.crt'

# Require SSL for all connections
hostssl all all 0.0.0.0/0 md5

Connection Encryption:

# Connect with SSL
psql "postgresql://user:pass@host/db?sslmode=require"

Application Security

Security Headers:

# nginx.conf
server {
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 "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'" always;
}

Rate Limiting:

# docker-compose.yml
services:
app:
environment:
- RATE_LIMIT_REQUESTS=100
- RATE_LIMIT_WINDOW=60

Monitoring

Health Checks

Docker Health Check:

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/api/health || exit 1

Kubernetes Liveness/Readiness:

livenessProbe:
httpGet:
path: /api/health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10

readinessProbe:
httpGet:
path: /api/ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5

Metrics

Prometheus Metrics:

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

grafana:
image: grafana/grafana:latest
ports:
- "3001:3000"

Available Metrics:

  • dotvault_requests_total - Total requests
  • dotvault_request_duration_seconds - Request latency
  • dotvault_active_users - Current active users
  • dotvault_secrets_total - Total secrets stored
  • dotvault_audit_events_total - Audit events

Logging

Structured Logging:

{
"timestamp": "2024-01-15T10:30:00Z",
"level": "info",
"message": "User logged in",
"userId": "user_xxx",
"ipAddress": "192.168.1.1",
"userAgent": "Mozilla/5.0..."
}

Log Aggregation:

# docker-compose.yml
services:
app:
logging:
driver: "fluentd"
options:
fluentd-address: localhost:24224
tag: docker.dotvault

Upgrades

Upgrade Procedure

  1. Backup:

    docker-compose exec -T db pg_dump -U dotvault dotvault > pre-upgrade-backup.sql
  2. Pull New Image:

    docker-compose pull
  3. Stop Services:

    docker-compose down
  4. Start New Version:

    docker-compose up -d
  5. Run Migrations:

    docker-compose exec app npm run db:migrate
  6. Verify:

    docker-compose logs -f app

Rollback

If issues detected:

# Stop services
docker-compose down

# Restore previous image
docker-compose pull dotvault/dotvault:previous-version

# Restore database
docker-compose exec -T db psql -U dotvault < pre-upgrade-backup.sql

# Start services
docker-compose up -d

Troubleshooting

Database Connection Issues

  1. Check PostgreSQL is running
  2. Verify connection string
  3. Check firewall rules
  4. Review PostgreSQL logs

Encryption Key Issues

  1. Verify key is 64 hex characters
  2. Check key hasn't changed
  3. Restore from backup if lost
  4. Contact support if data recovery needed

Performance Issues

  1. Check resource usage (CPU, memory)
  2. Review database query performance
  3. Enable connection pooling
  4. Scale horizontally if needed

SSL Certificate Issues

  1. Verify certificate not expired
  2. Check certificate chain complete
  3. Verify domain matches
  4. Review Traefik/nginx logs

Support