Skip to Content

Production Guide: Deploy Seafile with Docker Compose + Caddy + MariaDB on Ubuntu

Self-host a production-grade Dropbox alternative with HTTPS, persistent storage, and team file sync on your own server.

Seafile is a battle-tested open-source file sync and share platform used by universities, enterprises, and privacy-conscious teams as a self-hosted alternative to Dropbox or Google Drive. Hosting Seafile on your own server means no per-user billing, no vendor lock-in, and complete control over your data residency and retention policies. This guide walks through a production-grade deployment on Ubuntu 22.04 using Docker Compose, Caddy as the TLS-terminating reverse proxy, and MariaDB as the relational storage backend. You will end with a fully operational, HTTPS-accessible Seafile instance with persistent volumes, a least-privilege database user, and daily backup hooks.

Architecture and flow overview

The stack consists of four containers managed by a single Docker Compose file:

  • db — MariaDB 10.11 storing Seafile metadata, user accounts, share records, and file commit trees.
  • memcached — In-memory cache that accelerates Seahub (the Django web frontend) and reduces MariaDB query load.
  • seafile — The combined Seafile server and Seahub application container, exposed on an internal port only.
  • caddy — The reverse proxy that handles HTTPS certificate issuance via Let's Encrypt ACME and forwards HTTP/S traffic to the Seafile container.

Caddy listens on ports 80 and 443 on the host. All containers share a private seafile_net Docker bridge network. MariaDB is never exposed on the host network. Seafile authenticates against MariaDB using a dedicated application user with least-privilege grants. Persistent data lives in named Docker volumes (seafile_data, mariadb_data) so container restarts are non-destructive. File libraries are stored in Seafile's internal object storage format inside the seafile_data volume.

Prerequisites

  • Ubuntu 22.04 VPS with a public IPv4 (or IPv6) address
  • A domain name with an A record pointing to the server (e.g., files.example.com)
  • Docker Engine 24+ and Docker Compose v2 installed (docker compose version)
  • Ports 80 and 443 open in the host firewall (ufw allow 80,443/tcp)
  • At least 2 GB RAM and 20 GB disk space (Seafile's memory footprint at idle is roughly 800 MB)
  • An outbound SMTP relay or transactional email service if you need email verification and password reset

Step-by-step deployment

1) Create the project directory

mkdir -p /opt/seafile
cd /opt/seafile

2) Create the secrets file

Store all credentials in a .env file. Never commit this file to version control. Generate strong random values with openssl rand -hex 32.

cat > /opt/seafile/.env << 'EOF'
MARIADB_ROOT_PASSWORD=change_me_root_strong
MARIADB_DATABASE=seafile
MARIADB_USER=seafile
MARIADB_PASSWORD=change_me_seafile_strong
[email protected]
SEAFILE_ADMIN_PASSWORD=change_me_admin_strong
SEAFILE_SERVICE_URL=https://files.example.com
SEAFILE_SECRET_KEY=change_me_64char_hex_value
EOF
chmod 600 /opt/seafile/.env

3) Write the Docker Compose file

version: "3.9"

services:
  db:
    image: mariadb:10.11
    restart: unless-stopped
    env_file: .env
    environment:
      MYSQL_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MARIADB_DATABASE}
      MYSQL_USER: ${MARIADB_USER}
      MYSQL_PASSWORD: ${MARIADB_PASSWORD}
    volumes:
      - mariadb_data:/var/lib/mysql
    networks:
      - seafile_net

  memcached:
    image: memcached:1.6-alpine
    restart: unless-stopped
    command: memcached -m 256
    networks:
      - seafile_net

  seafile:
    image: seafileltd/seafile-mc:10.0-latest
    restart: unless-stopped
    env_file: .env
    environment:
      DB_HOST: db
      DB_ROOT_PASSWD: ${MARIADB_ROOT_PASSWORD}
      SEAFILE_ADMIN_EMAIL: ${SEAFILE_ADMIN_EMAIL}
      SEAFILE_ADMIN_PASSWORD: ${SEAFILE_ADMIN_PASSWORD}
      SEAFILE_SERVER_LETSENCRYPT: "false"
      SEAFILE_SERVER_HOSTNAME: files.example.com
      SEAFILE_SERVICE_URL: ${SEAFILE_SERVICE_URL}
      SEAFILE_SECRET_KEY: ${SEAFILE_SECRET_KEY}
      MEMCACHE_HOST: memcached
      MEMCACHE_PORT: "11211"
    volumes:
      - seafile_data:/shared
    depends_on:
      - db
      - memcached
    networks:
      - seafile_net

  caddy:
    image: caddy:2-alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    depends_on:
      - seafile
    networks:
      - seafile_net

networks:
  seafile_net:
    driver: bridge

volumes:
  mariadb_data:
  seafile_data:
  caddy_data:
  caddy_config:

4) Write the Caddyfile

files.example.com {
    reverse_proxy seafile:80
    encode gzip
    header {
        Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
        X-Content-Type-Options nosniff
        X-Frame-Options SAMEORIGIN
    }
}

Replace files.example.com with your actual domain in both the Caddyfile and the Compose environment variables.

5) Start the stack

docker compose up -d
docker compose logs -f seafile

The first start takes 60–120 seconds. Seafile initialises databases, runs migrations, and starts Seahub. Wait until the logs show Seahub started before opening the browser.

6) Open the browser and verify

Navigate to https://files.example.com and log in with the admin credentials from your .env file. You should see the Seafile dashboard with 0 files and 0 libraries.

Configuration and secrets handling

After the first start, Seafile writes its runtime configuration to /shared/seafile/conf/ inside the seafile_data volume. The three key files are:

  • seafile.conf — storage quotas, file size limits, WebDAV settings
  • seahub_settings.py — Django settings, SMTP, authentication backends, 2FA
  • ccnet.conf — server identity, network binding

To configure outbound email for password resets and share notifications, edit seahub_settings.py inside the volume:

docker compose exec seafile bash -c "cat >> /shared/seafile/conf/seahub_settings.py" << 'EOF'

EMAIL_USE_TLS = True
EMAIL_HOST = 'smtp.example.com'
EMAIL_HOST_USER = '[email protected]'
EMAIL_HOST_PASSWORD = 'your_smtp_password'
EMAIL_PORT = 587
DEFAULT_FROM_EMAIL = '[email protected]'
SERVER_EMAIL = '[email protected]'
EOF

Then restart the seafile container to pick up the change:

docker compose restart seafile

Security best practices:

  • Never store SMTP passwords in Compose environment; write them directly to seahub_settings.py inside the named volume and restrict file permissions (chmod 600).
  • Rotate SEAFILE_SECRET_KEY by updating the value in .env and in seahub_settings.py simultaneously; mismatched keys invalidate all active sessions.
  • Use a read-only MariaDB user for Seafile's application queries — grant only SELECT, INSERT, UPDATE, DELETE on the seafile database, not GRANT OPTION or ALL.
  • Back up seafile_data and mariadb_data volumes daily using a tool like Kopia or Restic before any upgrades.

Verification

Run these checks to confirm the deployment is healthy:

# All containers running
docker compose ps

# Caddy issued a valid TLS certificate
curl -I https://files.example.com | grep HTTP

# Seahub is reachable and returns a 200 or 302
curl -sL -o /dev/null -w "%{http_code}" https://files.example.com/

# MariaDB reachable from seafile container
docker compose exec seafile mysql -h db -u seafile -p"${MARIADB_PASSWORD}" -e "SHOW DATABASES;" 2>/dev/null | grep seafile

# Upload a test file via the CLI API
curl -sX POST https://files.example.com/api2/auth-token/ \
  -d "[email protected]&password=your_admin_password" | python3 -m json.tool

The final command returns a token object if authentication is working. A successful token response confirms the REST API, Seahub, and the database are all operational end-to-end.

Common issues and fixes

Seahub not starting — database connection refused

Seafile's container starts immediately but Seahub waits for MariaDB to be ready. If MariaDB takes more than 30 seconds, Seafile may log OperationalError: (2003, "Can't connect to MySQL server"). Add a healthcheck to the db service and a condition: service_healthy in seafile.depends_on to serialize startup correctly.

TLS certificate not issued — ACME challenge fails

Caddy performs HTTP-01 ACME challenges on port 80. If your firewall blocks port 80 or the domain's DNS A record does not resolve to the server's IP, certificate issuance will fail silently. Verify with curl http://files.example.com/.well-known/acme-challenge/test from an external machine before investigating Caddy logs.

File upload fails with 413 Request Entity Too Large

Caddy has no built-in upload size limit for reverse-proxied requests, but Seafile's Nginx internal sub-server (inside the container) limits request bodies to 200 MB by default. Increase the limit in /shared/seafile/conf/seafile.conf:

[fileserver]
max_upload_size = 10000

The value is in MB. Restart the container after the change.

Memcached connection errors in Seahub logs

If you see ConnectionRefusedError: [Errno 111] Connection refused when Seahub tries to reach Memcached, verify both containers are on the same Docker network with docker network inspect seafile_seafile_net and that the MEMCACHE_HOST environment variable matches the Compose service name exactly (memcached).

Admin password forgotten

Reset the admin password via the Seafile management command inside the running container:

docker compose exec seafile /opt/seafile/seafile-server-latest/reset-admin.sh

FAQ

Can I use Seafile with Active Directory or LDAP?

Yes. Seafile Community Edition supports LDAP authentication out of the box. Add the LDAP connection parameters to seahub_settings.py (server URL, base DN, bind credentials, and attribute mappings). Seafile Pro adds LDAP group sync and role-based access. The LDAP configuration does not require a container restart if you use docker compose exec seafile python3 manage.py check to validate before reloading.

How do I upgrade Seafile to a new major version?

Pull the new image tag, stop the stack, update the image reference in docker-compose.yml, and start the stack again. Seafile's container entrypoint detects the version bump and runs the appropriate database migration scripts automatically. Always back up seafile_data and mariadb_data volumes before upgrading. Skipping major versions (e.g., 9 → 11) is not supported; upgrade sequentially.

What is the difference between Seafile Community and Pro editions?

The Community Edition is fully open-source and free for any number of users. The Pro Edition adds full-text search inside document content, audit logging, LDAP group sync, Office Online/OnlyOffice integration, two-factor TOTP enforcement at the organisation level, and enhanced compliance export tools. For most self-hosted teams under 50 users, Community Edition is sufficient. Pro requires a licence key but is free for up to 3 users.

How do I enable WebDAV for desktop client mounts?

WebDAV is enabled by default in Seafile. Mount the endpoint at https://files.example.com/seafdav/ using any WebDAV-capable desktop client (Finder, Windows Explorer via Map Network Drive, or Dolphin). Set the path to /seafdav/ and authenticate with your Seafile username and password. For large file transfers, the Seafile native sync client is faster than WebDAV because it uses its own delta-sync protocol.

Can I migrate an existing Seafile instance to this Docker Compose setup?

Yes. Export your existing libraries with seaf-fsck and seaf-export, copy the /shared/seafile/ directory tree to the new volume, dump the MariaDB database, and import it into the new container's database. Run seaf-fsck.sh --repair after the data copy to detect and fix any block-level corruption introduced during the transfer. Test restores in a staging environment before cutting over production traffic.

How should I back up Seafile reliably?

A consistent Seafile backup requires two coordinated snapshots: a MariaDB dump (for metadata) and a file-system snapshot of the seafile_data volume (for file blocks). Take the database dump first while Seafile is running, then snapshot the volume. Restore in the reverse order: volume first, then import the SQL dump. Use Kopia, Restic, or Borgbackup scheduled via systemd timers or Docker labels with Ofelia to automate daily off-site backups.

Internal links

Talk to us

If you want this deployed with hardened access controls, monitoring standards, and production runbooks tailored to your environment, our team can help end-to-end.

Contact Us

Production Guide: Deploy Kimai with Docker Compose + Caddy + MariaDB on Ubuntu
Self-host the leading open-source time tracker with HTTPS, MariaDB persistence, and production hardening