Docker LXC Proxmox Seafile CE 13

I have issues getting Seafile to work in a docker lxc on Proxmox.
It was working fine in a VM with an SMB share, but I decided to create different VMs and LXCs for different services.

This is my current compose file:

services:
  db:
    image: ${SEAFILE_DB_IMAGE:-mariadb:10.11}
    container_name: seafile-mysql
    restart: unless-stopped
    environment:
      - MYSQL_ROOT_PASSWORD=${SEAFILE_MYSQL_ROOT_PASSWORD:-}
      - MYSQL_LOG_CONSOLE=true
      - MARIADB_AUTO_UPGRADE=1
    volumes:
      - "${SEAFILE_MYSQL_VOLUME:-/opt/seafile-mysql/db}:/var/lib/mysql"
    networks:
      - seafile-net
    healthcheck:
      test:
        [
          "CMD",
          "/usr/local/bin/healthcheck.sh",
          "--connect",
          "--mariadbupgrade",
          "--innodb_initialized",
        ]
      interval: 20s
      start_period: 30s
      timeout: 5s
      retries: 10

  redis:
    image: ${SEAFILE_REDIS_IMAGE:-redis}
    container_name: seafile-redis
    restart: unless-stopped
    command:
      - /bin/sh
      - -c
      - redis-server --requirepass "$$REDIS_PASSWORD"
    environment:
      - REDIS_PASSWORD=${REDIS_PASSWORD:-}
    networks:
      - seafile-net

  seafile:
    image: ${SEAFILE_IMAGE:-seafileltd/seafile-mc:13.0-latest}
    container_name: seafile
    restart: unless-stopped
    # ports:
    #   - "80:80"
    volumes:
      - ${SEAFILE_VOLUME:-/opt/seafile-data}:/shared
    environment:
      - SEAFILE_MYSQL_DB_HOST=${SEAFILE_MYSQL_DB_HOST:-db}
      - SEAFILE_MYSQL_DB_PORT=${SEAFILE_MYSQL_DB_PORT:-3306}
      - SEAFILE_MYSQL_DB_USER=${SEAFILE_MYSQL_DB_USER:-seafile}
      - SEAFILE_MYSQL_DB_PASSWORD=${SEAFILE_MYSQL_DB_PASSWORD:?Variable is not set or empty}
      - SEAFILE_MYSQL_ROOT_PASSWORD=${SEAFILE_MYSQL_ROOT_PASSWORD:-}
      - SEAFILE_MYSQL_DB_CCNET_DB_NAME=${SEAFILE_MYSQL_DB_CCNET_DB_NAME:-ccnet_db}
      - SEAFILE_MYSQL_DB_SEAFILE_DB_NAME=${SEAFILE_MYSQL_DB_SEAFILE_DB_NAME:-seafile_db}
      - SEAFILE_MYSQL_DB_SEAHUB_DB_NAME=${SEAFILE_MYSQL_DB_SEAHUB_DB_NAME:-seahub_db}
      - TIME_ZONE=${TIME_ZONE:-Etc/UTC}
      - SEAFILE_ADMIN_EMAIL=${SEAFILE_ADMIN_EMAIL:-me@example.com}
      - SEAFILE_ADMIN_PASSWORD=${SEAFILE_ADMIN_PASSWORD:-asecret}
      - SEAFILE_SERVER_HOSTNAME=${SEAFILE_SERVER_HOSTNAME:?Variable is not set or empty}
      - SEAFILE_SERVER_PROTOCOL=${SEAFILE_SERVER_PROTOCOL:-http}
      - SITE_ROOT=${SITE_ROOT:-/}
      - NON_ROOT=${NON_ROOT:-false}
      - JWT_PRIVATE_KEY=${JWT_PRIVATE_KEY:?Variable is not set or empty}
      - SEAFILE_LOG_TO_STDOUT=${SEAFILE_LOG_TO_STDOUT:-false}
      - ENABLE_SEADOC=${ENABLE_SEADOC:-true}
      - SEADOC_SERVER_URL=${SEAFILE_SERVER_PROTOCOL:-http}://${SEAFILE_SERVER_HOSTNAME:?Variable is not set or empty}/sdoc-server
      - CACHE_PROVIDER=${CACHE_PROVIDER:-redis}
      - REDIS_HOST=${REDIS_HOST:-redis}
      - REDIS_PORT=${REDIS_PORT:-6379}
      - REDIS_PASSWORD=${REDIS_PASSWORD:-}
      - MEMCACHED_HOST=${MEMCACHED_HOST:-memcached}
      - MEMCACHED_PORT=${MEMCACHED_PORT:-11211}
      - ENABLE_NOTIFICATION_SERVER=${ENABLE_NOTIFICATION_SERVER:-false}
      - INNER_NOTIFICATION_SERVER_URL=${INNER_NOTIFICATION_SERVER_URL:-http://notification-server:8083}
      -  NOTIFICATION_SERVER_URL=${NOTIFICATION_SERVER_URL:-${SEAFILE_SERVER_PROTOCOL:-http}://${SEAFILE_SERVER_HOSTNAME:?Variable is not set or empty}/notification}
      - ENABLE_SEAFILE_AI=${ENABLE_SEAFILE_AI:-false}
      - SEAFILE_AI_SERVER_URL=${SEAFILE_AI_SERVER_URL:-http://seafile-ai:8888}
      - SEAFILE_AI_SECRET_KEY=${JWT_PRIVATE_KEY:?Variable is not set or empty}
      - MD_FILE_COUNT_LIMIT=${MD_FILE_COUNT_LIMIT:-100000}
    labels:
      caddy: ${SEAFILE_SERVER_PROTOCOL:-http}://${SEAFILE_SERVER_HOSTNAME:?Variable is not set or empty}
      caddy.reverse_proxy: "{{upstreams 80}}"
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    networks:
      - seafile-net

  caddy:
    image: ${SEAFILE_CADDY_IMAGE:-lucaslorentz/caddy-docker-proxy:2.9-alpine}
    restart: unless-stopped
    container_name: seafile-caddy
    ports:
      - 80:80
      - 443:443
    environment:
      - CADDY_INGRESS_NETWORKS=seafile-net
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ${SEAFILE_CADDY_VOLUME:-/opt/seafile-caddy}:/data/caddy
    networks:
      - seafile-net
    healthcheck:
      test: ["CMD-SHELL", "curl --fail http://localhost:2019/metrics || exit 1"]
      start_period: 20s
      interval: 20s
      timeout: 5s
      retries: 3

  seadoc:
    image: ${SEADOC_IMAGE:-seafileltd/sdoc-server:2.0-latest}
    container_name: seadoc
    volumes:
      - ${SEADOC_VOLUME:-/opt/seadoc-data/}:/shared
    # ports:
    #   - "80:80"
    environment:
      - DB_HOST=${SEAFILE_MYSQL_DB_HOST:-db}
      - DB_PORT=${SEAFILE_MYSQL_DB_PORT:-3306}
      - DB_USER=${SEAFILE_MYSQL_DB_USER:-seafile}
      - DB_PASSWORD=${SEAFILE_MYSQL_DB_PASSWORD:?Variable is not set or empty}
      - DB_NAME=${SEADOC_MYSQL_DB_NAME:-seahub_db}
      - TIME_ZONE=${TIME_ZONE:-Etc/UTC}
      - JWT_PRIVATE_KEY=${JWT_PRIVATE_KEY:?Variable is not set or empty}
      - NON_ROOT=${NON_ROOT:-false}
      - SEAHUB_SERVICE_URL=${SEAFILE_SERVICE_URL:-http://seafile}
    labels:
      caddy: ${SEAFILE_SERVER_PROTOCOL:-http}://${SEAFILE_SERVER_HOSTNAME:?Variable is not set or empty}
      caddy.@ws.0_header: "Connection *Upgrade*"
      caddy.@ws.1_header: "Upgrade websocket"
      caddy.0_reverse_proxy: "@ws {{upstreams 80}}"
      caddy.1_handle_path: "/socket.io/*"
      caddy.1_handle_path.0_rewrite: "* /socket.io{uri}"
      caddy.1_handle_path.1_reverse_proxy: "{{upstreams 80}}"
      caddy.2_handle_path: "/sdoc-server/*"
      caddy.2_handle_path.0_rewrite: "* {uri}"
      caddy.2_handle_path.1_reverse_proxy: "{{upstreams 80}}"
    depends_on:
      db:
        condition: service_healthy
    networks:
      - seafile-net

networks:
  seafile-net:
    name: seafile-net

And this my .env:

## Images
SEAFILE_IMAGE=seafileltd/seafile-mc:13.0-latest
SEAFILE_DB_IMAGE=mariadb:10.11
SEAFILE_REDIS_IMAGE=redis
SEAFILE_CADDY_IMAGE=lucaslorentz/caddy-docker-proxy:2.9-alpine
SEADOC_IMAGE=seafileltd/sdoc-server:2.0-latest
NOTIFICATION_SERVER_IMAGE=seafileltd/notification-server:13.0-latest
MD_IMAGE=seafileltd/seafile-md-server:13.0-latest

## Persistent Storage
#BASIC_STORAGE_PATH=/opt
BASIC_STORAGE_PATH=/mnt/seafile
SEAFILE_VOLUME=$BASIC_STORAGE_PATH/seafile-data
SEAFILE_MYSQL_VOLUME=$BASIC_STORAGE_PATH/seafile-mysql/db
SEAFILE_CADDY_VOLUME=$BASIC_STORAGE_PATH/seafile-caddy
SEADOC_VOLUME=$BASIC_STORAGE_PATH/seadoc-data

#################################
#      Startup parameters       #
#################################
SEAFILE_SERVER_HOSTNAME=XXX
SEAFILE_SERVER_PROTOCOL=http
TIME_ZONE=Europe/Berlin
JWT_PRIVATE_KEY=xXX

#####################################
# Third-party service configuration #
#####################################

## Database
SEAFILE_MYSQL_DB_HOST=db
SEAFILE_MYSQL_DB_USER=seafile
SEAFILE_MYSQL_DB_PASSWORD=XXX
SEAFILE_MYSQL_DB_CCNET_DB_NAME=ccnet_db
SEAFILE_MYSQL_DB_SEAFILE_DB_NAME=seafile_db
SEAFILE_MYSQL_DB_SEAHUB_DB_NAME=seahub_db

## Cache
CACHE_PROVIDER=redis # or memcached

### Redis
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD=XXX

### Memcached
MEMCACHED_HOST=memcached
MEMCACHED_PORT=11211

######################################
#        Initial variables           #
# (Only valid in first-time startup) #
######################################

## Database root password, Used to create Seafile users
SEAFILE_MYSQL_ROOT_PASSWORD=XXX

## Seafile admin user
SEAFILE_ADMIN_EMAIL=XXX
SEAFILE_ADMIN_PASSWORD=XXX

############################################
# Additional configurations for extensions #
############################################

## SeaDoc service
ENABLE_SEADOC=true

## Notification
ENABLE_NOTIFICATION_SERVER=false
NOTIFICATION_SERVER_URL=

## Seafile AI
ENABLE_SEAFILE_AI=false
SEAFILE_AI_LLM_URL=
SEAFILE_AI_LLM_KEY=

## Metadata server
MD_FILE_COUNT_LIMIT=100000

I mounted the SMB share to the LXC like described here.
Proxmox Forum:
/threads/tutorial-unprivileged-lxcs-mount-cifs-shares.101795/

This works perfectly fine for other services and I’m able to create files and folder in the LXC on the share, and it creates all the folders for the DB and Seafile etc. but I get this error during startup for the mysql container:

seafile-mysql | 2025-08-08 14:44:14+00:00 [Note] [Entrypoint]: Initializing database files
seafile-mysql | 2025-08-08 14:44:14 0 [Warning] Can’t create test file ā€˜/var/lib/mysql/0ca35996907d.lower-test’ (Errcode: 13 ā€œPermission deniedā€)
seafile-mysql | /usr/sbin/mariadbd: Can’t change dir to ā€˜/var/lib/mysql/’ (Errcode: 13 ā€œPermission deniedā€)
seafile-mysql | 2025-08-08 14:44:14 0 [ERROR] Aborting

Anybody got this working already?

1 Like

Hello! Not sure if you were able to resolve this, but this error is usually due to permissions with cifs in unprivileged lxc. Based on my experience, Seafile requires mounts within privileged LXCs with proper ā€˜options’ set up in pve. Below are the steps I used to get a new Seafile 13 CE running on LXC.

I started from scratch using below conf file in an privileged lxc running template (ubuntu-24.04-standard_24.04-2_amd64.tar.zst):

arch: amd64
cores: 2
features: nesting=1,keyctl=1,mount=nfs
hostname: seafilev13
memory: 4096
nameserver: 1.1.1.1 8.8.8.8
net0: name=eth0,bridge=vmbr0,firewall=1,hwaddr=BC:24:11:26:38:65,ip=dhcp,type=veth
ostype: ubuntu
rootfs: local-lvm:vm-104-disk-0,size=48G
swap: 0
tags: document
lxc.apparmor.profile: unconfined
lxc.cgroup2.devices.allow: a
lxc.cap.drop:

Then, I mounted unraid nfs share to house the data volumes. I added below line in /etc/fstab (host bind mounts gave me headaches):

192.168.1.39:/mnt/user/Seafile-Main /mnt/nas/seafile-main nfs4 rw,_netdev,x-systemd.automount,x-systemd.idle-timeout=600,x-systemd.mount-timeout=10s,vers=4.2 0 0

This needs nfs utils to work

sudo apt-get update && sudo apt-get install -y nfs-common
sudo mkdir -p /mnt/nas/seafile-main
systemctl daemon-reload
systemctl restart remote-fs.target
mount /mnt/nas/seafile-main

On Unraid vm, my export settings are:

/mnt/user/Seafile-Main -fsid=101,async,no_subtree_check *(rw,sync,no_subtree_check,insecure,no_root_squash,anonuid=0,anongid=0)

Next, I created the dirs on my lxc:

mkdir -p     /opt/seafile-mysqldb \              /opt/seadoc-data \              /opt/seafile-caddy \              /opt/seafile chown -R $(id -u):$(id -g) /mnt/nas/seafile-main /opt/seadoc-data /opt/seafile-caddy /opt/seafile  

Now my ls -ltr looks like this for /opt:

root@seafilev13:/opt/seafile# ls -ltr /opt/
total 20
drwx--x--x 4 root root            4096 Oct  3 03:33 containerd
drwxr-xr-x 3 root root            4096 Oct  3 03:51 seadoc-data
drwxr-xr-x 3 root root            4096 Oct  3 03:51 seafile-caddy
drwxr-xr-x 2 root root            4096 Oct  3 03:53 seafile
drwxr-xr-x 8  999 systemd-journal 4096 Oct  3 03:53 seafile-mysqldb

Now you can pull the official sources:

wget -O .env https://manual.seafile.com/13.0/repo/docker/ce/env
wget https://manual.seafile.com/13.0/repo/docker/ce/seafile-server.yml
wget https://manual.seafile.com/13.0/repo/docker/seadoc.yml
wget https://manual.seafile.com/13.0/repo/docker/caddy.yml

Now, you can start customizing - ONLY CHANGE THE (.ENV) file.
Do not bother tinkering with other files - 90% of them lead to a deadend or compatibility issues, I spent 5 days (of 4-5 hrs each) down that rabbit hole, with chatgpt (gpt5-thinking) to help me, and still got nowhere.
First get this working, Take a backup on PVE, then experiment as desired.
Since I created and mounted /mnt/nas/seafile-main as data volume, in env file, change the below parameter:

SEAFILE_VOLUME=/mnt/nas/seafile-main

Final note - ensure that the JWT private key is 40chars.
you can generate one using command :

pwgen -s 40 1 2>/dev/null || head -c 32 /dev/urandom | base64

Good luck!