Office beginner ⏱ 20 minutes

Etherpad: Real-Time Collaborative Text Editor

Lightweight real-time document collaboration. Perfect for notes, meetings, and brainstorming.

By Homelab Team • 2025-01-15
#etherpad#collaboration#real-time#notes#docker

Introduction

Etherpad is a lightweight, highly customizable, real-time collaborative text editor that runs in your browser. Unlike heavy office suites, Etherpad focuses on one thing and does it exceptionally well: enabling multiple users to edit text documents simultaneously with minimal latency and maximum simplicity.

What is Etherpad?

Etherpad is an open-source collaborative editor that allows multiple users to work on the same document in real-time. Changes appear instantly for all connected users, with each author’s contributions highlighted in their own color. It’s the technology that powered the early days of Google Docs collaboration and has evolved into a mature, plugin-rich platform.

Why Choose Etherpad?

Lightweight and Fast

Etherpad requires minimal resources compared to full office suites. It runs smoothly on a Raspberry Pi with just 512MB RAM, making it perfect for homelabs with limited resources. Pages load in milliseconds, and synchronization happens in real-time with imperceptible latency.

True Real-Time Collaboration

Unlike document-based editors that sync every few seconds, Etherpad transmits every keystroke instantly. Multiple users can type in the same paragraph simultaneously without conflicts. The operational transformation algorithm ensures consistency across all clients.

No Account Required

Want to collaborate quickly? Just share a URL. No sign-ups, no authentication hassles. Users can optionally set their display name, but it’s not required. Perfect for quick team brainstorming sessions or public collaboration.

Extensive Plugin Ecosystem

Over 400 plugins available for features like:

  • Markdown support
  • Code syntax highlighting
  • Video chat integration
  • Drawing tools
  • Import/export to various formats
  • Administration dashboards
  • Custom themes

Privacy Focused

Self-hosting Etherpad means your collaborative documents never leave your infrastructure. No third-party services, no data mining, complete control over retention policies.

Etherpad vs Alternatives

FeatureEtherpadHedgeDocGoogle DocsNotion
Resource UsageVery LowLowN/AN/A
Real-time SyncInstantInstantFastMedium
No Login RequiredYesOptionalNoNo
Markdown NativePluginYesNoPartial
Self-HostedYesYesNoNo
Plugin System400+LimitedN/AN/A
Video ChatPluginNoYesNo
Best ForQuick NotesDocumentationFull OfficeKnowledge Base

Architecture Overview

Understanding Etherpad’s architecture:

┌────────────────────────────────────────┐
│       Users (Web Browsers)             │
└──────────┬──────────────┬──────────────┘
           │              │
    ┌──────▼──────┐  ┌───▼──────┐
    │   WebSocket │  │   HTTP   │
    └──────┬──────┘  └───┬──────┘
           │             │
    ┌──────▼─────────────▼──────┐
    │   Reverse Proxy + SSL     │
    └──────────┬────────────────┘
               │
    ┌──────────▼────────────┐
    │  Etherpad (Node.js)   │
    │  - Express Server     │
    │  - Socket.io          │
    │  - Plugin Manager     │
    └──────────┬────────────┘
               │
    ┌──────────▼────────────┐
    │     Database          │
    │  PostgreSQL/MySQL     │
    │  (or dirty.db)        │
    └───────────────────────┘

Key Components:

  • Node.js Server: Core application running on Express
  • Socket.io: WebSocket communication for real-time updates
  • Database: Stores pads, revisions, and metadata
  • Plugin System: Extends functionality
  • Reverse Proxy: HTTPS termination and load balancing

Prerequisites

Before deploying Etherpad:

  • Docker and Docker Compose installed
  • 512MB RAM minimum (1GB recommended)
  • 1 CPU core minimum
  • 5GB storage space
  • Domain name (optional, for SSL)
  • Basic understanding of Docker
  • Reverse proxy (optional, for HTTPS)

Basic Deployment: Standalone Etherpad

Let’s deploy a simple Etherpad instance using the built-in dirty.db database.

Step 1: Quick Start with Dirty DB

For testing or small deployments (1-10 users):

# Create directory
mkdir -p ~/etherpad && cd ~/etherpad

# Create environment file
cat > .env <<EOF
# Etherpad Configuration
ETHERPAD_TITLE=My Homelab Etherpad
ETHERPAD_PORT=9001

# Admin credentials
ADMIN_PASSWORD=your-secure-admin-password

# Session secret (generate with: openssl rand -hex 32)
SESSION_KEY=$(openssl rand -hex 32)

# Domain
DOMAIN=pad.yourdomain.com
EOF

chmod 600 .env

Create docker-compose.yml:

version: '3.8'

services:
  etherpad:
    image: etherpad/etherpad:latest
    container_name: etherpad
    restart: unless-stopped

    environment:
      # Basic configuration
      - TITLE=${ETHERPAD_TITLE}
      - DEFAULT_PAD_TEXT=Welcome to Etherpad! This is a collaborative document.

      # Admin user
      - ADMIN_PASSWORD=${ADMIN_PASSWORD}

      # Security
      - SESSION_KEY=${SESSION_KEY}
      - TRUST_PROXY=true

      # Performance
      - MINIFY=true

    ports:
      - "${ETHERPAD_PORT}:9001"

    volumes:
      # Persistent data using dirty.db
      - etherpad_data:/opt/etherpad-lite/var

    networks:
      - etherpad-net

volumes:
  etherpad_data:

networks:
  etherpad-net:
    driver: bridge

Launch Etherpad:

# Start the service
docker-compose up -d

# Check logs
docker-compose logs -f etherpad

# Wait for "You can access your Etherpad instance at http://localhost:9001/"

Access at http://localhost:9001 - create a new pad and start collaborating!

Production Deployment with PostgreSQL

For production use with better performance and reliability:

Step 1: Complete Stack Configuration

Update .env:

cat > .env <<EOF
# Etherpad Configuration
ETHERPAD_TITLE=Homelab Etherpad
ETHERPAD_PORT=9001
ADMIN_PASSWORD=your-secure-admin-password
SESSION_KEY=$(openssl rand -hex 32)

# PostgreSQL Configuration
POSTGRES_DB=etherpad
POSTGRES_USER=etherpad
POSTGRES_PASSWORD=your-secure-postgres-password

# Domain
DOMAIN=pad.yourdomain.com
EOF

Create production docker-compose.yml:

version: '3.8'

services:
  # PostgreSQL Database
  postgres:
    image: postgres:15-alpine
    container_name: etherpad-postgres
    restart: unless-stopped

    environment:
      - POSTGRES_DB=${POSTGRES_DB}
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}

    volumes:
      - postgres_data:/var/lib/postgresql/data

    networks:
      - etherpad-net

    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
      interval: 10s
      timeout: 5s
      retries: 5

  # Etherpad Application
  etherpad:
    image: etherpad/etherpad:latest
    container_name: etherpad
    restart: unless-stopped

    depends_on:
      postgres:
        condition: service_healthy

    environment:
      # Basic Configuration
      - TITLE=${ETHERPAD_TITLE}
      - DEFAULT_PAD_TEXT=Welcome to ${ETHERPAD_TITLE}!
      - ADMIN_PASSWORD=${ADMIN_PASSWORD}
      - SESSION_KEY=${SESSION_KEY}

      # Database Configuration
      - DB_TYPE=postgres
      - DB_HOST=postgres
      - DB_PORT=5432
      - DB_NAME=${POSTGRES_DB}
      - DB_USER=${POSTGRES_USER}
      - DB_PASS=${POSTGRES_PASSWORD}

      # Performance Settings
      - MINIFY=true
      - TRUST_PROXY=true
      - DISABLE_IP_LOGGING=false

      # Security
      - REQUIRE_SESSION=false
      - EDIT_ONLY=false
      - REQUIRE_AUTHENTICATION=false
      - REQUIRE_AUTHORIZATION=false

    ports:
      - "${ETHERPAD_PORT}:9001"

    volumes:
      # Plugin and settings storage
      - etherpad_plugins:/opt/etherpad-lite/node_modules
      - etherpad_data:/opt/etherpad-lite/var
      # Optional: custom settings
      - ./settings.json:/opt/etherpad-lite/settings.json:ro

    networks:
      - etherpad-net

    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9001/"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

volumes:
  postgres_data:
  etherpad_plugins:
  etherpad_data:

networks:
  etherpad-net:
    driver: bridge

Launch the stack:

docker-compose up -d
docker-compose logs -f

Advanced Configuration with Custom Settings

Create a custom settings.json for advanced control:

{
  "title": "My Homelab Etherpad",
  "favicon": "favicon.ico",
  "skinName": "colibris",
  "skinVariants": "dark-toolbar light-background super-light-editor",

  "ip": "0.0.0.0",
  "port": 9001,
  "showSettingsInAdminPage": true,

  "dbType": "postgres",
  "dbSettings": {
    "user": "etherpad",
    "host": "postgres",
    "database": "etherpad",
    "password": "your-password",
    "port": 5432
  },

  "defaultPadText": "Welcome to Etherpad!\n\nThis pad is synchronized as you type.",

  "padOptions": {
    "noColors": false,
    "showControls": true,
    "showChat": true,
    "showLineNumbers": true,
    "useMonospaceFont": false,
    "userName": "Anonymous",
    "userColor": false,
    "rtl": false,
    "alwaysShowChat": false,
    "chatAndUsers": false,
    "lang": "en-us"
  },

  "padShortcutEnabled": {
    "altF9": true,
    "altC": true,
    "delete": true,
    "return": true,
    "esc": true,
    "cmdShift2": true,
    "cmdZ": true,
    "cmdY": true,
    "cmdB": true,
    "cmdI": true,
    "cmdU": true,
    "cmd5": true,
    "cmdShiftL": true,
    "cmdShiftN": true,
    "cmdShift1": true,
    "cmdShiftC": true,
    "cmdH": true,
    "ctrlHome": true,
    "pageUp": true,
    "pageDown": true
  },

  "suppressErrorsInPadText": false,
  "requireSession": false,
  "editOnly": false,
  "minify": true,
  "maxAge": 21600,
  "abiword": null,
  "soffice": null,
  "tidyHtml": null,
  "allowUnknownFileEnds": true,
  "requireAuthentication": false,
  "requireAuthorization": false,
  "trustProxy": true,

  "cookie": {
    "sameSite": "Lax",
    "sessionLifetime": 864000000,
    "sessionRefreshInterval": 86400000
  },

  "users": {
    "admin": {
      "password": "your-admin-password",
      "is_admin": true
    }
  },

  "socketTransportProtocols": ["websocket", "polling"],
  "socketIo": {
    "maxHttpBufferSize": 10000000
  },

  "loadTest": false,
  "indentationOnNewLine": false,

  "toolbar": {
    "left": [
      ["bold", "italic", "underline", "strikethrough"],
      ["orderedlist", "unorderedlist", "indent", "outdent"],
      ["undo", "redo"],
      ["clearauthorship"]
    ],
    "right": [
      ["importexport", "timeslider", "savedrevision"],
      ["settings", "embed"],
      ["showusers"]
    ],
    "timeslider": [
      ["timeslider_export", "timeslider_settings", "timeslider_returnToPad"]
    ]
  },

  "ep_headings2": {
    "enabled": true
  },

  "loglevel": "INFO",
  "logconfig": {
    "appenders": [
      {
        "type": "console"
      }
    ]
  }
}

Mount in docker-compose.yml:

volumes:
  - ./settings.json:/opt/etherpad-lite/settings.json:ro

Plugin Installation and Management

Etherpad’s power comes from its plugin ecosystem.

Essential Plugins

Install via Admin panel or environment variables:

environment:
  # Install plugins on startup
  - INSTALL_PLUGINS=ep_headings2,ep_markdown,ep_comments_page,ep_align,ep_font_color,ep_table_of_contents,ep_author_hover,ep_adminpads2

Recommended Plugins:

  • ep_headings2: Add heading styles (H1-H6)
  • ep_markdown: Markdown support
  • ep_comments_page: Add comments to pads
  • ep_align: Text alignment options
  • ep_font_color: Text and background colors
  • ep_table_of_contents: Auto-generate TOC
  • ep_author_hover: Show author on hover
  • ep_adminpads2: Enhanced admin dashboard
  • ep_draw: Embedded drawing tool
  • ep_embedded_hyperlinks2: Rich link previews
  • ep_copy_paste_images: Paste images directly
  • ep_page_view: Page view mode
  • ep_print: Enhanced printing

Manual Plugin Installation

# Enter container
docker exec -it etherpad bash

# Install plugin
cd /opt/etherpad-lite
npm install ep_markdown

# Restart Etherpad
exit
docker-compose restart etherpad

Via Admin Panel

  1. Access http://pad.yourdomain.com/admin
  2. Login with admin credentials
  3. Go to Plugin Manager
  4. Search and install plugins
  5. Restart Etherpad

Reverse Proxy Configuration

Nginx Configuration

# /etc/nginx/sites-available/etherpad.conf

upstream etherpad {
    server localhost:9001;
}

server {
    listen 80;
    server_name pad.yourdomain.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name pad.yourdomain.com;

    # SSL Configuration
    ssl_certificate /etc/letsencrypt/live/pad.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/pad.yourdomain.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    # Security headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;

    # Increase timeouts for long-polling
    proxy_read_timeout 3600s;
    proxy_send_timeout 3600s;

    location / {
        proxy_pass http://etherpad;
        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;
        proxy_buffering off;

        # WebSocket support
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }

    # Static resources
    location ~ ^/(static|javascripts|stylesheets|images)/ {
        proxy_pass http://etherpad;
        proxy_cache_valid 200 30d;
        add_header Cache-Control "public, immutable";
    }
}

Traefik Configuration

services:
  etherpad:
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.etherpad.rule=Host(`pad.yourdomain.com`)"
      - "traefik.http.routers.etherpad.entrypoints=websecure"
      - "traefik.http.routers.etherpad.tls.certresolver=letsencrypt"
      - "traefik.http.services.etherpad.loadbalancer.server.port=9001"

      # WebSocket support
      - "traefik.http.middlewares.etherpad-headers.headers.customrequestheaders.X-Forwarded-Proto=https"

Caddy Configuration

pad.yourdomain.com {
    reverse_proxy etherpad:9001 {
        header_up X-Forwarded-Proto {scheme}
    }
}

Authentication and Security

Enable User Authentication

Update settings.json:

{
  "requireAuthentication": true,
  "requireAuthorization": false,

  "users": {
    "admin": {
      "password": "admin-password",
      "is_admin": true
    },
    "user1": {
      "password": "user1-password",
      "is_admin": false
    }
  }
}

LDAP Integration

Install ep_ldapauth plugin:

environment:
  - INSTALL_PLUGINS=ep_ldapauth

Configure in settings.json:

{
  "ep_ldapauth": {
    "url": "ldap://ldap.yourdomain.com",
    "accountBase": "ou=users,dc=yourdomain,dc=com",
    "accountPattern": "(&(objectClass=person)(uid={{username}}))",
    "displayNameAttribute": "cn",
    "searchDN": "cn=admin,dc=yourdomain,dc=com",
    "searchPWD": "admin-password",
    "groupSearchBase": "ou=groups,dc=yourdomain,dc=com",
    "groupAttribute": "member",
    "groupAttributeIsDN": true
  }
}

Rate Limiting

Protect against abuse:

{
  "commitRateLimiting": {
    "duration": 1,
    "points": 10
  }
}

Performance Optimization

Database Tuning

For PostgreSQL, optimize connection pooling:

{
  "dbSettings": {
    "max": 20,
    "min": 2,
    "idle": 10000,
    "acquire": 30000,
    "evict": 1000
  }
}

Enable Caching

{
  "minify": true,
  "maxAge": 86400,
  "cache": {
    "type": "memory",
    "maxAge": 86400000
  }
}

Resource Limits

services:
  etherpad:
    deploy:
      resources:
        limits:
          memory: 2G
          cpus: '2'
        reservations:
          memory: 512M
          cpus: '0.5'

Monitoring and Maintenance

Admin Dashboard

Access at https://pad.yourdomain.com/admin

Features:

  • Active pads list
  • User statistics
  • Plugin management
  • Pad management
  • Settings editor

Health Monitoring

# Check health
curl https://pad.yourdomain.com/stats

# Monitor logs
docker-compose logs -f etherpad

# Resource usage
docker stats etherpad

Backup Strategy

# Create backup script
cat > ~/etherpad-backup.sh <<'EOF'
#!/bin/bash
BACKUP_DIR="/mnt/backups/etherpad"
DATE=$(date +%Y%m%d_%H%M%S)

# Backup database
docker exec etherpad-postgres pg_dump -U etherpad etherpad > $BACKUP_DIR/etherpad-db-$DATE.sql

# Backup settings
docker cp etherpad:/opt/etherpad-lite/settings.json $BACKUP_DIR/settings-$DATE.json

# Backup plugins
docker exec etherpad tar czf /tmp/plugins-$DATE.tar.gz -C /opt/etherpad-lite/node_modules .
docker cp etherpad:/tmp/plugins-$DATE.tar.gz $BACKUP_DIR/

# Remove old backups
find $BACKUP_DIR -name "*.sql" -mtime +30 -delete
find $BACKUP_DIR -name "*.json" -mtime +30 -delete
find $BACKUP_DIR -name "*.tar.gz" -mtime +30 -delete

echo "Backup completed: $DATE"
EOF

chmod +x ~/etherpad-backup.sh

# Schedule daily backups
crontab -e
# Add: 0 3 * * * /home/user/etherpad-backup.sh

Integration with Nextcloud

While Etherpad doesn’t have native Nextcloud integration like ONLYOFFICE or Collabora, you can embed it:

# Install Etherpad Nextcloud app
docker exec -u www-data nextcloud php occ app:install etherpad_lite

# Configure Etherpad URL
docker exec -u www-data nextcloud php occ config:app:set etherpad_lite etherpad_url --value="https://pad.yourdomain.com"

# Set API key (if using ep_api_key plugin)
docker exec -u www-data nextcloud php occ config:app:set etherpad_lite etherpad_api_key --value="your-api-key"

Troubleshooting

Issue: Pads Not Saving

# Check database connection
docker exec etherpad npm run check-db

# Verify database
docker exec etherpad-postgres psql -U etherpad -d etherpad -c "dt"

# Check logs
docker-compose logs etherpad | grep -i error

Issue: WebSocket Connection Failed

# Verify WebSocket in browser console
# Should see: ws://pad.yourdomain.com/socket.io/?...

# Check reverse proxy WebSocket support
# Nginx: ensure "proxy_set_header Upgrade" is set
# Traefik: check middleware configuration

# Test direct connection
curl -i -N -H "Connection: Upgrade" -H "Upgrade: websocket" http://localhost:9001/socket.io/

Issue: Slow Performance

# Enable minification
# In settings.json: "minify": true

# Increase database pool
# In settings.json dbSettings: "max": 20

# Check resource usage
docker stats etherpad

# Upgrade to PostgreSQL if using dirty.db

Issue: Plugin Installation Failed

# Enter container and install manually
docker exec -it etherpad bash
cd /opt/etherpad-lite
npm install ep_markdown --no-save

# Check plugin compatibility
npm outdated

# Restart Etherpad
docker-compose restart etherpad

Advanced Use Cases

Public Collaboration Platform

{
  "requireAuthentication": false,
  "editOnly": false,
  "defaultPadText": "Share this URL with collaborators!",
  "padOptions": {
    "showChat": true,
    "showLineNumbers": true
  }
}

Private Team Workspace

{
  "requireAuthentication": true,
  "requireAuthorization": true,
  "editOnly": true,
  "users": {
    "team-member-1": {"password": "pass1", "is_admin": false},
    "team-member-2": {"password": "pass2", "is_admin": false}
  }
}

Education Platform

Install plugins for education:

  • ep_copy_paste_images
  • ep_draw
  • ep_comments_page
  • ep_page_view
  • ep_print

Conclusion

You now have a fully functional Etherpad deployment providing:

  • Lightweight, real-time collaborative editing
  • Extensive plugin ecosystem
  • Flexible authentication options
  • Production-ready performance
  • Complete data privacy

Next Steps

  1. Install essential plugins for your use case
  2. Configure authentication if needed
  3. Set up automated backups
  4. Integrate with other services
  5. Customize themes and branding

Resources

Your real-time collaborative platform is ready for production use!