Self-Hosting OpnForm with Docker Compose

What Is OpnForm?

OpnForm is an open-source form builder that lets you create forms, surveys, and quizzes without writing code. It offers a drag-and-drop editor, conditional logic, file uploads, webhooks, and integrations — similar to Typeform or Google Forms but running on your own server. Built with Laravel (PHP) and Nuxt (Vue.js), it provides a polished, modern interface for form creation and response management.

Updated February 2026: Verified with latest Docker images and configurations.

Official site: opnform.com | GitHub

Prerequisites

  • A Linux server (Ubuntu 22.04+ recommended)
  • Docker and Docker Compose installed (guide)
  • 2 GB of free disk space
  • 1 GB of RAM minimum (2 GB recommended)
  • A domain name (recommended for public forms)

Docker Compose Configuration

Create a project directory and the following files:

# docker-compose.yml
services:
  api:
    image: jhumanj/opnform-api:1.13.1
    container_name: opnform-api
    restart: unless-stopped
    env_file:
      - ./api.env
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    volumes:
      - opnform_storage:/app/storage

  api-worker:
    image: jhumanj/opnform-api:1.13.1
    container_name: opnform-worker
    restart: unless-stopped
    command: php artisan queue:work
    env_file:
      - ./api.env
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    volumes:
      - opnform_storage:/app/storage

  api-scheduler:
    image: jhumanj/opnform-api:1.13.1
    container_name: opnform-scheduler
    restart: unless-stopped
    command: php artisan schedule:work
    env_file:
      - ./api.env
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started

  ui:
    image: jhumanj/opnform-client:1.13.1
    container_name: opnform-ui
    restart: unless-stopped
    environment:
      NUXT_PUBLIC_APP_URL: https://forms.example.com
      NUXT_PUBLIC_API_BASE: /api

  ingress:
    image: nginx:1.27-alpine
    container_name: opnform-ingress
    restart: unless-stopped
    ports:
      - "8080:80"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - api
      - ui

  db:
    image: postgres:16-alpine
    container_name: opnform-db
    restart: unless-stopped
    environment:
      POSTGRES_DB: opnform
      POSTGRES_USER: opnform
      POSTGRES_PASSWORD: change-this-db-password
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U opnform"]
      interval: 10s
      timeout: 5s
      retries: 5

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

volumes:
  postgres_data:
  opnform_storage:
  redis_data:

Create api.env alongside your compose file:

# Application settings
APP_ENV=production
APP_KEY=base64:GENERATE_WITH_php_artisan_key_generate
APP_DEBUG=false
APP_URL=https://forms.example.com

# Database
DB_CONNECTION=pgsql
DB_HOST=db
DB_PORT=5432
DB_DATABASE=opnform
DB_USERNAME=opnform
DB_PASSWORD=change-this-db-password

# Redis
REDIS_HOST=redis
REDIS_PORT=6379

# PHP settings
PHP_MEMORY_LIMIT=512M
PHP_MAX_EXECUTION_TIME=300
PHP_UPLOAD_MAX_FILESIZE=64M

# Mail (optional — for form response notifications)
MAIL_MAILER=smtp
MAIL_HOST=smtp.example.com
MAIL_PORT=587
MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_ENCRYPTION=tls
[email protected]
MAIL_FROM_NAME="OpnForm"

Create nginx.conf for the reverse proxy:

server {
    listen 80;
    client_max_body_size 64m;

    location /api/ {
        proxy_pass http://api:8000/;
        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;
    }

    location / {
        proxy_pass http://ui:3000;
        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;
    }
}

Generate an application key:

docker compose run --rm api php artisan key:generate --show

Copy the output (starts with base64:) and paste it as the APP_KEY value in api.env.

Start the stack:

docker compose up -d

Run database migrations:

docker compose exec api php artisan migrate --force

Initial Setup

Access OpnForm at http://your-server:8080. Create your first admin account through the registration form. After registration, you can start building forms immediately using the drag-and-drop editor.

Key first steps:

  1. Create your admin account
  2. Set up email notifications in api.env if you want form submission alerts
  3. Create your first form using the visual editor
  4. Share the form URL or embed it on your website

Configuration

Form Features

OpnForm supports:

  • Field types: Text, email, number, date, select, multi-select, checkbox, file upload, rating, signature, phone number
  • Conditional logic: Show/hide fields based on previous answers
  • Form logic: Thank-you pages, redirect after submission, webhook notifications
  • Styling: Custom themes, colors, fonts, and branding
  • Embedding: iframe or JavaScript embed on external sites
  • Pre-filling: URL parameters to pre-fill form fields
  • Submissions: View, export (CSV), and filter responses

Email Notifications

Configure SMTP settings in api.env to receive email notifications when forms are submitted. OpnForm supports notification emails to form creators and confirmation emails to respondents.

File Uploads

File uploads are stored in the opnform_storage volume. The default upload limit is 64 MB per file (set via PHP_UPLOAD_MAX_FILESIZE and the nginx client_max_body_size). Adjust both values if you need larger uploads.

Webhooks

OpnForm can send form submission data to external URLs via webhooks. Configure webhooks per form in the form settings. Useful for integrating with automation tools like n8n or Huginn.

Reverse Proxy

The built-in Nginx ingress listens on port 8080. For production, place it behind your main reverse proxy with SSL:

Nginx Proxy Manager / Caddy / Traefik: Point your domain to localhost:8080 and enable SSL. Update APP_URL in api.env and NUXT_PUBLIC_APP_URL in the ui service to match your domain.

Reverse Proxy Setup

Backup

Back up the PostgreSQL database and storage volume:

# Database backup
docker compose exec db pg_dump -U opnform opnform > opnform-backup-$(date +%Y-%m-%d).sql

# Storage backup (uploaded files)
docker compose cp api:/app/storage ./storage-backup-$(date +%Y-%m-%d)

Backup Strategy

Troubleshooting

Forms Not Loading After Setup

Symptom: Blank page or API errors when accessing OpnForm. Fix: Verify APP_URL and NUXT_PUBLIC_APP_URL match your actual domain. Run docker compose exec api php artisan config:cache after changing environment variables.

File Uploads Failing

Symptom: Upload errors on large files. Fix: Increase both PHP_UPLOAD_MAX_FILESIZE in api.env and client_max_body_size in nginx.conf. Restart all containers.

Queue Jobs Not Processing

Symptom: Emails not sending, webhooks not firing. Fix: Check the worker container: docker compose logs api-worker. Verify Redis is running: docker compose exec redis redis-cli ping (should return PONG).

Resource Requirements

  • RAM: ~400 MB idle (API + worker + scheduler + UI + DB + Redis), ~600 MB under load
  • CPU: Low to medium
  • Disk: ~500 MB for application, plus storage for file uploads

Verdict

OpnForm is a strong self-hosted alternative to Typeform and JotForm. The form builder is polished, conditional logic works well, and the submission management interface is clean. It requires more infrastructure than simpler alternatives (6 containers including worker, scheduler, and nginx), but the feature set justifies the complexity. For teams that need a full-featured form builder without SaaS subscriptions, OpnForm is the best open-source option alongside Formbricks.

Comments