Hi Arraro,

A post on how I crafted my current SeaFile system, on which I’ve spent too much time…

So I won’t detail the early steps needed to get SeaFile running on your guest host, this is well documented here .

My requirement’s were the followings:

  • SeaFile to run on it’s guest host on HTTPS:443
  • SeaFile available on multiple FQDN’s (internal/external)
  • SeaFile behaving like a good boy when interfaced through a publicly reachable reverse-proxy (pheeewwww that was loooooong and haaaaaard)

In preamble, the latest editions of SeaFile are pushing hard for you to swallow the Docker pill that seems now glued with it. I know my ways within Docker but in whole honesty, I’m not in favor of having that piece of kit all over the place and pretty frankly, could I have avoided it on SeaFile I’d have gone without it.. But as explained, I think it’s now the default path and further releases may not even be possible as bare metal on your guest host. Alright, perhaps that is room for thoughts on future possibilities for my self hosted file storage… Though for now, SeaFile it is…

Here you’d see a high level overview at how things are now shed down in combination with the Docker part.

Getting rid of the embedded Caddy Reverse Proxy#

I tried all I could to love the newly embedded Caddy Reverse Proxy – I even read out loud some Yoda quotes to possibly apply The Force at it – although, I couldn’t get this to work the way I wanted it to. The main issue being that it was an either HTTP OR HTTPS all in one setup. I needed something less rigid and I wanted to terminate the SSL/TLS connections at another externally reachable reverse-proxy. Using the per default embedded Caddy Reverse Proxy, I was continuously facing Bad Request - Contradictory scheme headers errors this while interfaced from the publicly reachable reverse-proxy. It seems that I wasn’t the only one…

So here is what I’ve done to get rid of Caddy within my config.

Below is the needed seafile-server.yml which the above linked setup procedure requires you to download, in which I did a few modifications where I’ve put some comments.

root@seafile:/opt/seafile# cat seafile-server.yml
services:
  db:
    image: ${SEAFILE_DB_IMAGE:-mariadb:10.11}
    container_name: seafile-mysql
    restart: unless-stopped
    environment:
      - MYSQL_ROOT_PASSWORD=${INIT_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

  memcached:
    image: ${SEAFILE_MEMCACHED_IMAGE:-memcached:1.6.29}
    container_name: seafile-memcached
    restart: unless-stopped
    entrypoint: memcached -m 256
    networks:
      - seafile-net

  elasticsearch:
    image: ${SEAFILE_ELASTICSEARCH_IMAGE:-elasticsearch:8.15.0}
    container_name: seafile-elasticsearch
    restart: unless-stopped
    environment:
      - discovery.type=single-node
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms2g -Xmx2g"
      - "xpack.security.enabled=false"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    mem_limit: 4g
    volumes:
      - "${SEAFILE_ELASTICSEARCH_VOLUME:-/opt/seafile-elasticsearch/data}:/usr/share/elasticsearch/data"
    networks:
      - seafile-net

  seafile:
    image: ${SEAFILE_IMAGE:-seafileltd/seafile-pro-mc:12.0-latest}
    container_name: seafile
    restart: unless-stopped
    ports:
      # we'll deal with you later on young jedi !
      - "127.0.0.1:8000:80"
    volumes:
      - ${SEAFILE_VOLUME:-/opt/seafile-data}:/shared
    environment:
      - DB_HOST=${SEAFILE_MYSQL_DB_HOST:-db}
      - DB_PORT=${SEAFILE_MYSQL_DB_PORT:-3306}
      - DB_USER=${SEAFILE_MYSQL_DB_USER:-seafile}
      - DB_ROOT_PASSWD=${INIT_SEAFILE_MYSQL_ROOT_PASSWORD:-}
      - DB_PASSWORD=${SEAFILE_MYSQL_DB_PASSWORD:?Variable is not set or empty}
      - 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}
      - INIT_SEAFILE_ADMIN_EMAIL=${INIT_SEAFILE_ADMIN_EMAIL:-me@example.com}
      - INIT_SEAFILE_ADMIN_PASSWORD=${INIT_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
      - INIT_S3_STORAGE_BACKEND_CONFIG=${INIT_S3_STORAGE_BACKEND_CONFIG:-false}
      - INIT_S3_COMMIT_BUCKET=${INIT_S3_COMMIT_BUCKET:-}
      - INIT_S3_FS_BUCKET=${INIT_S3_FS_BUCKET:-}
      - INIT_S3_BLOCK_BUCKET=${INIT_S3_BLOCK_BUCKET:-}
      - INIT_S3_KEY_ID=${INIT_S3_KEY_ID:-}
      - INIT_S3_SECRET_KEY=${INIT_S3_SECRET_KEY:-}
      - INIT_S3_USE_V4_SIGNATURE=${INIT_S3_USE_V4_SIGNATURE:-true}
      - INIT_S3_AWS_REGION=${INIT_S3_AWS_REGION:-us-east-1}
      - INIT_S3_HOST=${INIT_S3_HOST:-}
      - INIT_S3_USE_HTTPS=${INIT_S3_USE_HTTPS:-true}
    # No Caddy for me Danke !
    #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
    #  memcached:
    #    condition: service_started
    #  elasticsearch:
    #    condition: service_started
    networks:
      - seafile-net

networks:
  seafile-net:
    name: seafile-net

And this is the needed seadoc.yml which again, the linked setup procedure requires you to download, in which I did a few modifications where I’ve put some comments.

root@seafile:/opt/seafile# cat seadoc.yml
services:

  seadoc:
    image: ${SEADOC_IMAGE:-seafileltd/sdoc-server:1.0-latest}
    container_name: seadoc
    restart: unless-stopped
    volumes:
      - ${SEADOC_VOLUME:-/opt/seadoc-data/}:/shared
    ports:
      # we'll deal with you later !!!
      - "127.0.0.1:8888: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}
    # No Cadddy needed, obrigado!
    #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 finally, here is the .env needed file as well (note: the caddy.yml file isn’t called, every references to caddy are commented, the requested SeaFile protocol is set to https / credentials have been removed):

COMPOSE_FILE='seafile-server.yml,seadoc.yml'
COMPOSE_PATH_SEPARATOR=','

# Seafile & Basic services

## Images
SEAFILE_IMAGE=seafileltd/seafile-pro-mc:12.0-latest
SEAFILE_DB_IMAGE=mariadb:10.11
SEAFILE_MEMCACHED_IMAGE=memcached:1.6.29
SEAFILE_ELASTICSEARCH_IMAGE=elasticsearch:8.15.0
# no caddy
#SEAFILE_CADDY_IMAGE=lucaslorentz/caddy-docker-proxy:2.9-alpine

## Persistent Storage
SEAFILE_VOLUME=/opt/seafile-data
SEAFILE_MYSQL_VOLUME=/opt/seafile-mysql/db
SEAFILE_ELASTICSEARCH_VOLUME=/opt/seafile-elasticsearch/data
# no caddy
#SEAFILE_CADDY_VOLUME=/opt/seafile-caddy

## Database
SEAFILE_MYSQL_DB_HOST=db
SEAFILE_MYSQL_DB_USER=xx
SEAFILE_MYSQL_DB_PASSWORD=xx

## Scheme
SEAFILE_SERVER_HOSTNAME=your.fqdn.suffix
SEAFILE_SERVER_PROTOCOL=https

## Startup parameters
TIME_ZONE=Etc/UTC
JWT_PRIVATE_KEY=xx

## Initial variables (can be removed after firstime startup)

### Database root password
INIT_SEAFILE_MYSQL_ROOT_PASSWORD=xx

### Seafile admin user
INIT_SEAFILE_ADMIN_EMAIL=xx
INIT_SEAFILE_ADMIN_PASSWORD=xx

### S3
INIT_S3_STORAGE_BACKEND_CONFIG=false
INIT_S3_COMMIT_BUCKET=<your-commit-objects>
INIT_S3_FS_BUCKET=<your-fs-objects>
INIT_S3_BLOCK_BUCKET=<your-block-objects>
INIT_S3_KEY_ID=<your-key-id>
INIT_S3_SECRET_KEY=<your-secret-key>
INIT_S3_USE_V4_SIGNATURE=true
INIT_S3_AWS_REGION=us-east-1
INIT_S3_HOST=
INIT_S3_USE_HTTPS=true

# SeaDoc service
ENABLE_SEADOC=true
SEADOC_IMAGE=seafileltd/sdoc-server:1.0-latest
SEADOC_VOLUME=/opt/seadoc-data

# Notification server
NOTIFICATION_SERVER_IMAGE=seafileltd/notification-server:12.0-latest
NOTIFICATION_SERVER_VOLUME=/opt/notification-data

# SeaSearch

## Image
#SEASEARCH_IMAGE=seafileltd/seasearch-nomkl:0.9-latest # Apple's Chip
SEASEARCH_IMAGE=seafileltd/seasearch:0.9-latest

## Storage
SS_STORAGE_TYPE=disk # disk (local storage), s3, oss

### Local storage mode
SS_DATA_PATH=/opt/seasearch-data # Persistent storage path
SS_MAX_OBJ_CACHE_SIZE=10GB

### S3 mode
SS_S3_USE_V4_SIGNATURE=false
SS_S3_ACCESS_ID=<your access id>
SS_S3_ACCESS_SECRET=<your access secret>
SS_S3_ENDPOINT=
SS_S3_BUCKET=<your bucket name>
SS_S3_USE_HTTPS=true
SS_S3_PATH_STYLE_REQUEST=true
SS_S3_AWS_REGION=us-east-1
SS_S3_SSE_C_KEY=<your SSE-C key>

## Log
SS_LOG_TO_STDOUT=false
SS_LOG_OUTPUT=true
SS_LOG_LEVEL=info

## Initial variables (can be removed after firstime startup SeaSearch service)
INIT_SS_ADMIN_USER=xx
INIT_SS_ADMIN_PASSWORD=xx

Installing a host based nginx reverse-proxy#

Once there, I then wanted to host an nginx reverse-proxy directly on the SeaFile host itself, that is, out of the Docker context. There is a SeaFile official documentation for using other reverse-proxy – here is what I’ve done (mind your own server_name…):

root@seafile:~# apt install nginx
root@seafile:~# mv /etc/nginx/sites-available/default /etc/nginx/sites-available/default.org
root@seafile:~#
root@seafile:~#
root@seafile:~#
root@seafile:~# /bin/bin/cat << 'EOF' > /etc/nginx/sites-available/default
log_format seafileformat '$http_x_forwarded_for $remote_addr [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" $upstream_response_time';

proxy_request_buffering off;
client_body_buffer_size 70m;
ssl_buffer_size 70m;
client_header_buffer_size 50k;
large_client_header_buffers 2 50k;
client_max_body_size 0;

server {
    listen       80;
    server_name  your.fqdn.suffix;
    rewrite ^ https://$http_host$request_uri? permanent;          # Forced redirect from HTTP to HTTPS

    server_tokens off;                                            # Prevents the Nginx version from being displayed in the HTTP response header
}

server {
    listen 443 ssl;
    ssl_certificate /etc/ssl.custom/certs/cert.pem;               # Path to your fullchain.pem
    ssl_certificate_key /etc/ssl.custom/private/key.pem;          # Path to your privkey.pem
    server_name your.fqdn.suffix;
    server_tokens off;

    location / {
         proxy_pass         http://127.0.0.1:8000;
         proxy_set_header   Host $host;
         proxy_set_header   X-Forwarded-Ssl off;
         proxy_set_header   X-Forwarded-Proto http;
         proxy_set_header   X-Real-IP $remote_addr;
         proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
         proxy_set_header   X-Forwarded-Host $server_name;
         proxy_read_timeout 1200s;


         # used for view/edit office file via Office Online Server
         client_max_body_size 0;

         access_log      /var/log/nginx/seahub.access.log seafileformat;
         error_log       /var/log/nginx/seahub.error.log;
    }

    location /sdoc-server/ {
        proxy_pass         http://127.0.0.1:8888/;
        proxy_redirect     off;
        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-Host  $server_name;

        client_max_body_size 100m;
    }

    location /socket.io {
        proxy_pass http://127.0.0.1:8888/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_redirect off;

        proxy_buffers 8 32k;
        proxy_buffer_size 64k;

        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_set_header X-NginX-Proxy true;
    }

}
EOF

root@seafile:~#
root@seafile:~# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
root@seafile:~#
root@seafile:~# systemctl enable nginx.service
root@seafile:~# systemctl restart nginx.service
root@seafile:~#

And here are a few details of the subsystem status after the above modifications:

root@seafile:~# docker container list --all
CONTAINER ID   IMAGE                                   COMMAND                  CREATED        STATUS                  PORTS                    NAMES
a724877f61f7   seafileltd/sdoc-server:1.0-latest       "/sbin/my_init -- /s…"   16 hours ago   Up 16 hours             127.0.0.1:8888->80/tcp   seadoc
44b7e32fc74c   mariadb:10.11                           "docker-entrypoint.s…"   16 hours ago   Up 16 hours (healthy)   3306/tcp                 seafile-mysql
f69b9e660642   seafileltd/seafile-pro-mc:12.0-latest   "/sbin/my_init -- /s…"   16 hours ago   Up 16 hours             127.0.0.1:8000->80/tcp   seafile
50d185e1d452   elasticsearch:8.15.0                    "/bin/tini -- /usr/l…"   16 hours ago   Up 16 hours             9200/tcp, 9300/tcp       seafile-elasticsearch
16aa9208b516   memcached:1.6.29                        "memcached -m 256"       16 hours ago   Up 16 hours             11211/tcp                seafile-memcached
root@seafile:~#
root@seafile:~# ss -utnlp
Netid         State          Recv-Q         Send-Q                 Local Address:Port                  Peer Address:Port        Process
...
tcp           LISTEN         0              4096                       127.0.0.1:8888                       0.0.0.0:*            users:(("docker-proxy",pid=867689,fd=7))
tcp           LISTEN         0              4096                       127.0.0.1:8000                       0.0.0.0:*            users:(("docker-proxy",pid=867645,fd=7))
tcp           LISTEN         0              511                          0.0.0.0:443                        0.0.0.0:*            users:(("nginx",pid=832947,fd=8))
tcp           LISTEN         0              511                          0.0.0.0:80                         0.0.0.0:*            users:(("nginx",pid=832947,fd=7))
...
root@seafile:~#
root@seafile:~#
root@seafile:~#

With this setup I can now tranquillo engage toward my SeaFile host from my publicly reachable reverse-proxy, which is typically where I’d do all my filtering, bad behaviors checks, Geo Location policies etc etc..

Hope this helps.
obruno