Skip to main content

Overview

This guide covers deploying OpnForm to production using Docker Compose. This is the recommended method for self-hosting OpnForm.
Before deploying to production, review the production considerations guide.

Prerequisites

  • Docker Engine 20.10+ and Docker Compose v2+
  • Server with at least 2GB RAM (4GB+ recommended)
  • Domain name with DNS configured
  • SSL certificate (or Let’s Encrypt setup)
  • Basic knowledge of Docker and Linux administration

Architecture

The production Docker setup includes:
  • api: Laravel API server (PHP-FPM + Nginx)
  • api-worker: Background job processor
  • api-scheduler: Scheduled task runner
  • ui: Nuxt 3 client application
  • db: PostgreSQL 16 database
  • redis: Redis for caching and queues
  • ingress: Nginx reverse proxy

Installation

1
Clone the Repository
2
git clone https://github.com/OpnForm/OpnForm.git
cd OpnForm
3
Configure Environment Variables
4
Create and configure the API environment file:
5
cp api/.env.example api/.env
6
Edit api/.env with your production settings:
7
# Core Settings
APP_NAME="OpnForm"
APP_ENV=production
APP_DEBUG=false
APP_URL=https://yourdomain.com

# Generate secure keys
APP_KEY=base64:YOUR_GENERATED_KEY_HERE
JWT_SECRET=YOUR_GENERATED_JWT_SECRET_HERE

# Frontend URL
FRONT_URL=https://yourdomain.com
FRONT_API_SECRET=YOUR_SECURE_SECRET_HERE

# Database Configuration
DB_CONNECTION=pgsql
DB_HOST=db
DB_PORT=5432
DB_DATABASE=opnform
DB_USERNAME=opnform
DB_PASSWORD=YOUR_SECURE_DB_PASSWORD

# Redis Configuration
REDIS_HOST=redis
REDIS_PASSWORD=null
REDIS_PORT=6379

# Cache & Queue Settings
CACHE_STORE=redis
QUEUE_CONNECTION=redis
SESSION_DRIVER=redis

# Mail Configuration
MAIL_MAILER=smtp
MAIL_HOST=your-smtp-host.com
MAIL_PORT=587
MAIL_USERNAME=your-smtp-username
MAIL_PASSWORD=your-smtp-password
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=[email protected]
MAIL_FROM_NAME="${APP_NAME}"

# Storage Configuration
FILESYSTEM_DRIVER=local
# For production with S3:
# FILESYSTEM_DRIVER=s3
# AWS_ACCESS_KEY_ID=your-key
# AWS_SECRET_ACCESS_KEY=your-secret
# AWS_DEFAULT_REGION=us-east-1
# AWS_BUCKET=your-bucket

# Captcha (optional but recommended)
H_CAPTCHA_SITE_KEY=your-hcaptcha-site-key
H_CAPTCHA_SECRET_KEY=your-hcaptcha-secret

# Admin Configuration
ADMIN_EMAILS=[email protected]
8
Never commit .env files with production credentials to version control.
9
Generate Application Keys
10
Generate secure keys for your application:
11
# Generate APP_KEY (run on your local machine with PHP)
php -r "echo 'base64:' . base64_encode(random_bytes(32)) . PHP_EOL;"

# Generate JWT_SECRET
php -r "echo bin2hex(random_bytes(32)) . PHP_EOL;"

# Generate FRONT_API_SECRET
php -r "echo bin2hex(random_bytes(32)) . PHP_EOL;"
12
Update these values in your api/.env file.
13
Configure Client Environment
14
Create and configure the client environment file:
15
cp client/.env.example client/.env
16
Edit client/.env:
17
NUXT_PUBLIC_APP_URL=https://yourdomain.com
NUXT_PUBLIC_API_BASE=https://yourdomain.com
NUXT_API_SECRET=YOUR_FRONT_API_SECRET_FROM_API_ENV
NUXT_PUBLIC_ENV=production

# Optional: Analytics
# NUXT_PUBLIC_GOOGLE_ANALYTICS_CODE=UA-XXXXXXXXX-X
# NUXT_PUBLIC_AMPLITUDE_CODE=your-amplitude-key

# Optional: Support Chat
# NUXT_PUBLIC_CRISP_WEBSITE_ID=your-crisp-id

# Captcha (must match API configuration)
NUXT_PUBLIC_H_CAPTCHA_SITE_KEY=your-hcaptcha-site-key
18
The NUXT_API_SECRET must match the FRONT_API_SECRET in api/.env.
19
Configure Database Credentials
20
Set database credentials in a root .env file or export them:
21
echo "DB_DATABASE=opnform" > .env
echo "DB_USERNAME=opnform" >> .env
echo "DB_PASSWORD=YOUR_SECURE_DB_PASSWORD" >> .env
22
Start the Application
23
Launch all services:
24
docker compose up -d
25
Monitor the startup process:
26
docker compose logs -f
27
Run Database Migrations
28
Once the API container is healthy, run migrations:
29
docker exec opnform-api php artisan migrate --force
30
Create Initial Admin Account
31
Navigate to https://yourdomain.com/setup in your browser to create the admin account.
32
The /setup route is only accessible when no admin users exist. Complete this step immediately after deployment.

Service Configuration

Container Resources

The docker-compose.yml configures PHP with the following limits:
environment:
  PHP_MEMORY_LIMIT: "1G"
  PHP_MAX_EXECUTION_TIME: "600"
  PHP_UPLOAD_MAX_FILESIZE: "64M"
  PHP_POST_MAX_SIZE: "64M"
Adjust these in your docker-compose.yml if needed for your workload.

Persistent Volumes

The following volumes persist data:
  • postgres-data: PostgreSQL database
  • opnform_storage: Uploaded files and application storage
  • redis-data: Redis cache and queue data
These volumes are automatically created and managed by Docker.

Health Checks

All services include health checks:
  • API: Checks Laravel is responding (php artisan about)
  • API Worker: Verifies queue worker process is running
  • API Scheduler: Confirms scheduler is executing
  • UI: Tests HTTP endpoint availability
  • Database: PostgreSQL readiness check
  • Redis: Ping/pong test
  • Ingress: Nginx configuration test + HTTP check

SSL/HTTPS Configuration

The default setup exposes port 80. For production, you need SSL: Use Nginx, Caddy, or Traefik in front of OpnForm:
  1. Keep ingress on port 80
  2. Configure your reverse proxy to:
    • Terminate SSL
    • Proxy to http://localhost:80
    • Set appropriate headers (X-Forwarded-*)

Option 2: Let’s Encrypt with Certbot

Mount certificates into the ingress container:
ingress:
  volumes:
    - ./docker/nginx.conf:/etc/nginx/templates/default.conf.template
    - /etc/letsencrypt:/etc/letsencrypt:ro
  ports:
    - "443:443"
    - "80:80"
Update docker/nginx.conf to handle SSL.

Managing the Deployment

View Logs

# All services
docker compose logs -f

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

Restart Services

# All services
docker compose restart

# Specific service
docker compose restart api

Stop the Application

docker compose down

Update OpnForm

# Pull latest images
docker compose pull

# Restart with new images
docker compose up -d

# Run migrations
docker exec opnform-api php artisan migrate --force

# Clear cache
docker exec opnform-api php artisan cache:clear
docker exec opnform-api php artisan config:clear

Backup and Recovery

Database Backup

# Create backup
docker exec opnform-db pg_dump -U opnform opnform > backup_$(date +%Y%m%d_%H%M%S).sql

# Restore backup
cat backup_20240315_120000.sql | docker exec -i opnform-db psql -U opnform -d opnform

Storage Backup

# Backup uploaded files
docker run --rm -v opnform_storage:/data -v $(pwd):/backup \
  alpine tar czf /backup/storage_$(date +%Y%m%d_%H%M%S).tar.gz -C /data .

# Restore storage
docker run --rm -v opnform_storage:/data -v $(pwd):/backup \
  alpine tar xzf /backup/storage_20240315_120000.tar.gz -C /data

Troubleshooting

Container Won’t Start

Check logs for the specific service:
docker compose logs api
Common issues:
  • Missing environment variables
  • Invalid database credentials
  • Port conflicts (80/443 already in use)

Database Connection Errors

Verify database is healthy:
docker compose ps db
Check database credentials match between .env and api/.env.

Permission Issues

Ensure storage volumes have correct permissions:
docker exec opnform-api chmod -R 775 /usr/share/nginx/html/storage
docker exec opnform-api chown -R www-data:www-data /usr/share/nginx/html/storage

Worker Not Processing Jobs

Check worker logs:
docker compose logs api-worker
Verify Redis connection and queue configuration in api/.env.

Next Steps

  • Review production considerations
  • Set up monitoring and alerting
  • Configure automated backups
  • Review security hardening
  • Set up log aggregation

Support

For deployment issues:

Build docs developers (and LLMs) love