Seadoc does not work in a Podman setup

Hello everyone,

I am having problem setting up seadoc using podman. I know that seafile does not officially support podman, but I prefer it over docker even though I am by no means an expert and try to learn.

My setup is the following:
All my services run behind a caddy reverse proxy on a rented vps which then uses wireguard to tunnel into my home server where my applications a running.
The seafile server (version 13) runs inside a pod which also has a redis cache container, a mariadb database container and the seafile-notification container running inside the same pod. This work without any problems up until now, and I am very happy with it. At first, I wanted to just seadoc into the same pod, but seafile and seadoc both have a web server running which both will try to bind on the port 80 inside the container and therefore this was not possible. Now I try to run seadoc in a separate container and just talk to the seafile pod via the podman network.

When I try to open a file inside the seafile web interface I get the following error:
Load doc content error

I already tried to trouble shoot using Gemini, Copilot and Claude, but I somehow can not get things to work. My guess is that either there is a handshake problem between seafile and seadoc or maybe seadoc can not connect to some of the other containers. I will add my caddyfile and my quadlet files:

seafile.domain.ch {
    handle /seafdav/* {
        reverse_proxy 10.10.0.2:6233
    }

    handle /sdoc-server/* {
        reverse_proxy 10.10.0.2:6232
    }

    handle /socket.io/* {
        reverse_proxy 10.10.0.2:6232 {
            header_up Connection {http.request.header.Connection}
            header_up Upgrade {http.request.header.Upgrade}
        }
    }

    handle /seafhttp/* {
        reverse_proxy 10.10.0.2:8082
    }

    handle /notification/* {
        reverse_proxy 10.10.0.2:6234 {
            header_up Connection {http.request.header.Connection}
            header_up Upgrade {http.request.header.Upgrade}
        }
    }

    reverse_proxy 10.10.0.2:8088 {
        header_up Host {host}
        header_up X-Real-IP {remote_host}
        header_up X-Forwarded-For {remote_host}
        header_up X-Forwarded-Proto {scheme}
	header_up X-Forwarded-Host {host}
    }
}
[Pod]
PodName=seafile
PublishPort=8088:80 #seafile-server
PublishPort=6233:8080 #webdav
PublishPort=6234:8083 #notifications
PublishPort=8082:8082 #fileserver
PublishPort=3306:3306 #mariadb
PublishPort=6379:6379 #redis


[Service]
Restart=always

[Install]
WantedBy=default.target

[Unit]
Description=Seafile Server (Community Edition)
After=seafile-db.service seafile-redis.service
Requires=seafile-pod.service seafile-db.service seafile-redis.service

[Container]
ContainerName=seafile-server
Image=docker.io/seafileltd/seafile-mc:13.0-latest
Pod=seafile.pod

Label=io.containers.autoupdate=registry

EnvironmentFile=/etc/containers/systemd/seafile.env

Volume=/mnt/storage/seafile/data:/shared:Z

[Service]
Restart=on-failure
TimeoutStartSec=300

[Install]
WantedBy=default.target

[Unit]
Description=Seafile SeaDoc Server

[Container]
ContainerName=seafile-seadoc
Image=docker.io/seafileltd/sdoc-server:2.0-latest
PublishPort=6232:7070

Label=io.containers.autoupdate=registry

EnvironmentFile=/etc/containers/systemd/seafile.env

Environment=SEAFILE_MYSQL_DB_HOST=10.88.0.1:3306
Environment=REDIS_HOST=10.88.0.1:6379

Environment=SEAHUB_SERVICE_URL=https://seafile.domain.ch

Volume=/mnt/storage/seafile/sdoc:/shared:Z

[Service]
Restart=on-failure
TimeoutStartSec=120

[Install]
WantedBy=default.target

[Unit]
Description=Seafile MariaDB Database
After=seafile-pod.service
Requires=seafile-pod.service

[Container]
ContainerName=seafile-db
Image=docker.io/mariadb:lts
Pod=seafile.pod

Label=io.containers.autoupdate=registry

Volume=/mnt/storage/seafile/db:/var/lib/mysql:Z

EnvironmentFile=/etc/containers/systemd/seafile.env
Environment=MARIADB_ROOT_PASSWORD=password
Environment=MYSQL_INITDB_SKIP_TZINFO=1
Environment=MYSQL_LOG_CONSOLE=true
Environment=MARIADB_AUTO_UPGRADE=1

HealthCmd=healthcheck.sh --connect --innodb_initialized
HealthInterval=20s
HealthRetries=10
HealthTimeout=5s
HealthStartPeriod=30s
Notify=healthy

[Service]
Restart=on-failure

[Install]
WantedBy=default.target

[Unit]
Description=Seafile Redis Cache
After=seafile-pod.service
Requires=seafile-pod.service

[Container]
ContainerName=seafile-redis
Image=docker.io/redis:7-alpine
Pod=seafile.pod

Label=io.containers.autoupdate=registry

HealthCmd=redis-cli ping || exit 1
HealthInterval=10s
HealthRetries=5
Notify=healthy

[Service]
Restart=on-failure

[Install]
WantedBy=default.target
[Unit]
Description=Seafile Notification Server
After=seafile-server.service
Requires=seafile-pod.service

[Container]
ContainerName=seafile-notification
Image=docker.io/seafileltd/notification-server:13.0-latest
Pod=seafile.pod

Label=io.containers.autoupdate=registry

EnvironmentFile=/etc/containers/systemd/seafile.env

Volume=/mnt/storage/seafile/data/seafile/logs:/shared/seafile/logs:Z

[Service]
Restart=on-failure

[Install]
WantedBy=default.target
The Environment file:


TIME_ZONE=Europe/Somewhere

SEAFILE_SERVER_HOSTNAME=seafile.domain.ch
SEAFILE_SERVER_PROTOCOL=https


JWT_PRIVATE_KEY=key

# ─── Datenbank ────────────────────────────────────────────────────────────────
# Im Pod kommunizieren alle Container über localhost
SEAFILE_MYSQL_DB_HOST=127.0.0.1
SEAFILE_MYSQL_DB_PORT=3306
SEAFILE_MYSQL_DB_USER=seafile
SEAFILE_MYSQL_DB_PASSWORD=password

# Nur beim allerersten Start benötigt:
INIT_SEAFILE_MYSQL_ROOT_PASSWORD=password

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 (Redis) ────────────────────────────────────────────────────────────
CACHE_PROVIDER=redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379

# ─── Admin-Account (nur Erstinstallation) ─────────────────────────────────────
INIT_SEAFILE_ADMIN_EMAIL=name@protonmail.com
INIT_SEAFILE_ADMIN_PASSWORD=password

# ─── SeaDoc ───────────────────────────────────────────────────────────────────
ENABLE_SEADOC=true
SEADOC_SERVER_URL=https://seafile.domain.ch/sdoc-server
SEADOC_VOLUME=/mnt/storage/seafile/sdoc

I would be very grateful if someone could help me to get this to work. There can not be much missing. All container run and I can ping seadoc from the seafile container and I can pink seafile from the seadoc container and seafile works fine.

Thank you in advance.

It looks like you have a well-structured Podman setup, but the “Load doc content error” typically indicates a communication or authentication failure between the sdoc-server and the Seafile server.

Based on your configuration and common issues with SeaDoc 2.0, here are the most likely causes:

1. Missing JWT_PRIVATE_KEY

In SeaDoc 2.0 and Seafile 13, both services must share the same JWT_PRIVATE_KEY for authentication. I noticed it is missing from your seafile-seadoc container environment.

  • Ensure JWT_PRIVATE_KEY is defined in your seafile.env or explicitly added to the SeaDoc container.
  • It must match the key used in the Seafile server container.

2. Internal Connectivity

The sdoc-server needs to be able to reach the Seafile server to retrieve document content.

  • In your config, SEAHUB_SERVICE_URL is set to your external domain (https://seafile.domain.ch).
  • Test: Exec into your seafile-seadoc container and try to curl that URL. If the container cannot resolve or reach the external domain (common in split-brain DNS or restricted container networking), the load will fail.
  • If it cannot reach the external URL, you may need to add a host entry to the container or use an internal service URL if your proxy allows it.

3. Database Table

SeaDoc requires a specific table in your MariaDB seahub_db called sdoc_operation_log.

  • While this is usually created automatically during Seafile 13 initialization, verify it exists in the database.
  • Ensure the SeaDoc container has correct permissions to write to the seahub_db.

Next Steps for Debugging:

Please check the logs inside the SeaDoc container:
podman exec -it seafile-seadoc tail -f /shared/logs/sdoc-server.log

Look for specific errors like:

  • invalid signature or jwt malformed (points to JWT key issue).
  • Request failed with status code 404 or Connection refused (points to connectivity/proxy issues).
  • Query data from Database error (points to DB access issues).

For more details, you can refer to the SeaDoc Integration Manual.

First of all, thank you Daniel, for your reply.

Missing JWR_PRIVATE_KEY
I am sure this is not the problem because it is defined in my seafile.env which is included in both my containers. Therefore it has to be the same everywhere.

Internal Connectivity
I tried again to curl the seafile domain from the seadoc container and get the following output:

podman exec -it seafile-seadoc curl -v https://seafile.domain.ch

Host seafile.domain.ch:443 was resolved.

IPv6: (none)

IPv4: ip-address

Trying ip-address:443…

Connected to seafile.domain.ch (ip-address) port 443

ALPN: curl offers h2,http/1.1

TLSv1.3 (OUT), TLS handshake, Client hello (1):

CAfile: /etc/ssl/certs/ca-certificates.crt

CApath: /etc/ssl/certs

TLSv1.3 (IN), TLS handshake, Server hello (2):

TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):

TLSv1.3 (IN), TLS handshake, Certificate (11):

TLSv1.3 (IN), TLS handshake, CERT verify (15):

TLSv1.3 (IN), TLS handshake, Finished (20):

TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):

TLSv1.3 (OUT), TLS handshake, Finished (20):

SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / X25519 / id-ecPublicKey

ALPN: server accepted h2

Server certificate:

subject: CN=seafile.domain.ch

start date: May 19 16:49:16 2026 GMT

expire date: Aug 17 16:49:15 2026 GMT

subjectAltName: host “seafile.domain.ch” matched cert’s “seafile.domain.ch”

issuer: C=US; O=Let’s Encrypt; CN=E8

SSL certificate verify ok.

Certificate level 0: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA384

Certificate level 1: Public key type EC/secp384r1 (384/192 Bits/secBits), signed using sha256WithRSAEncryption

Certificate level 2: Public key type RSA (4096/152 Bits/secBits), signed using sha256WithRSAEncryption

TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):

using HTTP/2

[HTTP/2] [1] OPENED stream for https://seafile.domain.ch/

[HTTP/2] [1] [:method: GET]

[HTTP/2] [1] [:scheme: https]

[HTTP/2] [1] [:authority: seafile.domain.ch]

[HTTP/2] [1] [:path: /]

[HTTP/2] [1] [user-agent: curl/8.5.0]

[HTTP/2] [1] [accept: /]

GET / HTTP/2
Host: seafile.domain.ch
User-Agent: curl/8.5.0
Accept: /

< HTTP/2 302
< access-control-allow-origin: *
< alt-svc: h3=“:443”; ma=2592000
< content-language: en
< content-type: text/html; charset=utf-8
< date: Sun, 14 Jun 2026 08:16:48 GMT
< location: /accounts/login/?next=/
< server: nginx
< vary: Accept-Language, Cookie
< via: 1.1 Caddy
< content-length: 0
<

Connection #0 to host seafile.domain.ch left intact

I also seem to be able to reach the mariadb and redis container.

podman exec -it seafile-seadoc curl -v telnet://10.88.0.1:3306

*   Trying 10.88.0.1:3306...

* Connected to 10.88.0.1 (10.88.0.1) port 3306

Warning: Binary output can mess up your terminal. Use "--output -" to tell 

Warning: curl to output it to your terminal anyway, or consider "--output 

Warning: <FILE>" to save to a file.

* Failure writing output to destination

* Closing connection

podman exec -it seafile-seadoc curl -v telnet://10.88.0.1:6379* Trying 10.88.0.1:6379...* Connected to 10.88.0.1 (10.88.0.1) port 6379

I was able to find some error which came from the caddyfile. I had do change my caddy file in the following way:

handle /sdoc-server/* {
    uri strip_prefix /sdoc-server
    reverse_proxy 10.10.0.2:6232
}


handle /seafhttp/* {
    uri strip_prefix /seafhttp
    reverse_proxy 10.10.0.2:8082
}

So I had to remove the prefixes in the domain, which is probably because I used Caddy and not nginx.

I am now getting a new error on the seadoc website:

The content of the document does not conform to the sdoc specification

And I also looked at the seadoc log which now reads the following:


[2026-06-14 11:47:39] [INFO] excalidraw-manager.js[101] - 0 docs saved.
[2026-06-14 11:49:13] [ERROR] document-controller.js[56] - Error connecting to Database
[2026-06-14 11:49:13] [ERROR] document-controller.js[94] - Error connecting to Database
[2026-06-14 11:52:39] [INFO] document-manager.js[104] - 0 docs saved.
[2026-06-14 11:52:39] [INFO] excalidraw-manager.js[101] - 0 docs saved.
[2026-06-14 11:57:39] [INFO] document-manager.js[104] - 0 docs saved.
[2026-06-14 11:57:39] [INFO] excalidraw-manager.js[101] - 0 docs saved.
[2026-06-14 12:00:04] [ERROR] document-controller.js[56] - Error connecting to Database
[2026-06-14 12:00:04] [ERROR] document-controller.js[94] - Error connecting to Database
[2026-06-14 12:00:12] [ERROR] document-controller.js[56] - Error connecting to Database
[2026-06-14 12:00:12] [ERROR] document-controller.js[94] - Error connecting to Database
[2026-06-14 12:02:39] [INFO] document-manager.js[104] - 0 docs saved.
[2026-06-14 12:02:39] [INFO] excalidraw-manager.js[101] - 0 docs saved.
[2026-06-14 12:03:32] [ERROR] document-controller.js[56] - Error connecting to Database
[2026-06-14 12:03:32] [ERROR] document-controller.js[94] - Error connecting to Database
[2026-06-14 12:07:39] [INFO] document-manager.js[104] - 0 docs saved.
[2026-06-14 12:07:39] [INFO] excalidraw-manager.js[101] - 0 docs saved.

I already checked using curl that I can reach the database and I checked the password and database name and both are correct, and I can log in using that.

It is not clear to me why the seadoc server cannot establish a connection to the database.

The “The content of the document does not conform to the sdoc specification” error in the UI is likely a side effect of the database connection failure. When the SeaDoc server encounters a database error, it returns an internal server error (500) which the browser can’t parse as a valid document.

Here is the offical seadoc yml file if deployed alone:

services:

  seadoc:
    image: ${SEADOC_IMAGE:-seafileltd/sdoc-server:2.0-latest}
    container_name: seadoc
    restart: unless-stopped
    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}
      - NON_ROOT=${NON_ROOT:-false}
      - JWT_PRIVATE_KEY=${JWT_PRIVATE_KEY:?Variable is not set or empty}
      - SEAHUB_SERVICE_URL=${SEAFILE_SERVICE_URL:-${SEAFILE_SERVER_PROTOCOL:-http}://${SEAFILE_SERVER_HOSTNAME:?Variable is not set or empty}}
    networks:
      - seafile-net

networks:
  seafile-net:
    name: seafile-net

Based on your logs and Quadlet configuration, here are the most likely causes for the database connection issue:

1. Separate Host and Port

In your SeaDoc Quadlet, you have:
Environment=SEAFILE_MYSQL_DB_HOST=10.88.0.1:3306

The SeaDoc server (a Node.js application) typically expects the host and port as separate variables. Try splitting them:

Environment=DB_HOST=10.88.0.1
Environment=DB_PORT=3306

2. Environment Variable Names

While the Seafile server uses variables prefixed with SEAFILE_MYSQL_, the SeaDoc container image specifically looks for these environment variables:

  • DB_HOST
  • DB_PORT
  • DB_USER
  • DB_PASSWORD
  • DB_NAME (This should be your seahub_db name)

Ensure these are either in your seafile.env or explicitly defined in the SeaDoc Quadlet. You can verify what variables the container is actually seeing by running:
podman exec seafile-seadoc env

3. Database Permissions

Ensure your MariaDB user (usually seafile) is allowed to connect from the SeaDoc container’s IP address. Since SeaDoc is in a separate container, MariaDB sees the connection coming from the Podman network (e.g., 10.88.0.x).

You can check the permissions in MariaDB:
SELECT user, host FROM mysql.user WHERE user = 'seafile';
If it is restricted to localhost or a specific internal IP that doesn’t match the SeaDoc container, you may need to grant access:
GRANT ALL PRIVILEGES ON seahub_db.* TO 'seafile'@'10.88.0.%' IDENTIFIED BY 'your_password';

4. Verify the Table

SeaDoc 2.0 requires a specific table in seahub_db. Even if you can connect, if the table is missing, it might log a database error. Check if it exists:
USE seahub_db; SHOW TABLES LIKE 'sdoc_operation_log';

For reference, you can check the standalone deployment section of the SeaDoc Integration Manual for the expected variable names.

Dear Daniel,

Again thank you very much for your answer/time!!

  1. Separate Host and Port

You are absolutely right that Environment=SEAFILE_MYSQL_DB_HOST=10.88.0.1:3306 is wrong, and I already fixed that part.

  1. Environment Variable Names

This is a good point at this was wrong in my setup up until now. For now I just added the Environment variables the direct way to the container via


Environment=DB_HOST=10.88.0.1
Environment=DB_PORT=3306
Environment=DB_USER=seafile
Environment=DB_PASSWORD=aRandomPassword
Environment=DB_NAME=seahub_db
  1. Database Permissions

I tried to chech what the permissions are for database using

SELECT User, Host FROM mysql.user WHERE User = 'seafile';

which gives me the output

+---------+------+
| User    | Host |
+---------+------+
| seafile | %    |
+---------+------+

As far as I understand it this should mean that if does not care about the ip address from where the traffic comes.

  1. Verify the Table

I get the following output

USE seahub_db; SHOW TABLES LIKE 'sdoc_operation_log';
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
+------------------------------------------+
| Tables_in_seahub_db (sdoc_operation_log) |
+------------------------------------------+
| sdoc_operation_log                       |
+------------------------------------------+

I think this means that the database it is looking for is present.

When I try to open a file I still get the error 500 with

The content of the document does not conform to the sdoc specification

I also checked the database log when I try to open a file and get the following output

2026-06-16 18:48:08 30156 [Warning] Access denied for user 'seafile'@'10.88.0.1' (using password: YES)
2026-06-16 18:49:06 30167 [Warning] Access denied for user 'seafile'@'10.88.0.1' (using password: YES)
2026-06-16 19:01:30 30247 [Warning] Access denied for user 'seafile'@'10.88.0.1' (using password: YES)
2026-06-16 19:01:31 30252 [Warning] Access denied for user 'seafile'@'10.88.0.1' (using password: YES)

I do not really understand why it says that the access is denied when it is trying to use the password. I also checked

podman exec seafile-seadoc env

and get the following output

DEBIAN_FRONTEND=teletype
SEAFILE_MYSQL_DB_SEAHUB_DB_NAME=seahub_db
CACHE_PROVIDER=redis
INIT_SEAFILE_ADMIN_EMAIL=mail@protonmail.com
DB_USER=seafile
INIT_SEAFILE_ADMIN_PASSWORD=password
ENABLE_SEADOC=true
container=podman
SEADOC_VOLUME=/mnt/storage/seafile/sdoc
DB_NAME=seahub_db
SEAFILE_MYSQL_DB_PORT=3306
DB_HOST=10.88.0.1
SEAFILE_SERVER_PROTOCOL=https
LANGUAGE=en_US:en
LANG=en_US.UTF-8
JWT_PRIVATE_KEY=key
TIME_ZONE=Europe/Zurich
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
SEAFILE_MYSQL_DB_SEAFILE_DB_NAME=seafile_db
INIT_SEAFILE_MYSQL_ROOT_PASSWORD=password2
DB_PORT=3306
SEAFILE_MYSQL_DB_USER=seafile
REDIS_PORT=6379
LC_ALL=en_US.UTF-8
server_version=2.0.9
SEAFILE_MYSQL_DB_HOST=127.0.0.1
REDIS_HOST=10.88.0.1
DB_PASSWORD=password
SEAFILE_MYSQL_DB_PASSWORD=password
SEAFILE_MYSQL_DB_CCNET_DB_NAME=ccnet_db
SEAHUB_SERVICE_URL=https://seafile.domain.ch
SEADOC_SERVER_URL=https://seafile.domain.ch/sdoc-server
SEAFILE_SERVER_HOSTNAME=seafile.domain.ch
HOME=/root

The MariaDB logs clearly show that the connection is reaching the database, but it is being rejected because of an incorrect password:

[Warning] Access denied for user 'seafile'@'10.88.0.1' (using password: YES)

Based on the environment variables you shared from podman exec seafile-seadoc env, there is a discrepancy:

  1. You mentioned setting Environment=DB_PASSWORD=aRandomPassword in your Quadlet file.
  2. However, the output of the env command shows DB_PASSWORD=password and SEAFILE_MYSQL_DB_PASSWORD=password.

This indicates that the SeaDoc container is still using the value password instead of aRandomPassword. This usually happens for one of two reasons:

  • Environment File Override: Your EnvironmentFile=/etc/containers/systemd/seafile.env likely contains DB_PASSWORD=password or SEAFILE_MYSQL_DB_PASSWORD=password, which is overriding the values you set directly in the Quadlet.
  • Systemd Not Reloaded: After modifying a Quadlet file, you must reload the systemd daemon and restart the service for the changes to take effect:
    systemctl --user daemon-reload
    systemctl --user restart seafile-seadoc
    

Recommendations:

  1. Check seafile.env: Open /etc/containers/systemd/seafile.env and ensure the passwords there match what is configured in your MariaDB container. If they are different, update them.
  2. Verify Password: Ensure the password for the 'seafile'@'%' user in MariaDB is actually what you think it is. You can test it from the SeaDoc container manually:
    podman exec -it seafile-seadoc mariadb -h 10.88.0.1 -u seafile -p
    
  3. Clean up Environment Variables: In your env output, I see both DB_HOST=10.88.0.1 and SEAFILE_MYSQL_DB_HOST=127.0.0.1. While SeaDoc 2.0 primarily uses DB_HOST, having conflicting variables can be confusing. It’s best to keep them consistent across your .env file and Quadlet.

Once the env output shows the correct DB_PASSWORD, the 500 error should be resolved.

Dear Daniel,

I just took out the .env file from the seadoc container and just add the Environments directly and now have the following Variables:

DB_PORT=3306
DB_HOST=10.88.0.1
DB_NAME=seahub_db
DB_PASSWORD=Password
DB_USER=seafile
JWT_PRIVATE_KEY=key
SEAHUB_SERVICE_URL=https://seafile.domain.ch
HOSTNAME=6cdb089aeae1
DEBIAN_FRONTEND=teletype
server_version=2.0.9
LANG=en_US.UTF-8
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
container=podman
LANGUAGE=en_US:en
LC_ALL=en_US.UTF-8
HOME=/root

I can also confirm that the password does work, as I can sign into the database using

DB_USER=seafile
DB_PASSWORD=Password

and the database also seams to be correct for that user

SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| ccnet_db           |
| information_schema |
| seafile_db         |
| seahub_db          |
+--------------------+

and also the privileges seem to be correct:

 SHOW GRANTS FOR 'seafile'@'%';
+--------------------------------------------------------------------------------------------------------+
| Grants for seafile@%                                                                                   |
+--------------------------------------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO `seafile`@`%` IDENTIFIED BY PASSWORD 'aHashOfThePassword' |
| GRANT ALL PRIVILEGES ON `ccnet_db`.* TO `seafile`@`%`                                                  |
| GRANT ALL PRIVILEGES ON `seahub_db`.* TO `seafile`@`%`                                                 |
| GRANT ALL PRIVILEGES ON `seafile_db`.* TO `seafile`@`%`                                                |
+--------------------------------------------------------------------------------------------------------+

It is good to see that the environment variables are now correctly aligned and that you can manually connect to the database from within the SeaDoc container.

Since you’ve updated the DB_PASSWORD to match, please confirm if you are still seeing the Access denied warnings in your MariaDB logs and the Error connecting to Database in the SeaDoc logs when you attempt to open a document.

If the errors persist despite the password being correct, here are two likely causes to investigate:

1. MariaDB Authentication Plugin

The Node.js driver used by SeaDoc can sometimes have trouble with certain authentication plugins. Check which plugin the seafile user is using:

SELECT user, host, plugin FROM mysql.user WHERE user = 'seafile';

If it is using caching_sha2_password, you might need to switch it to mysql_native_password for better compatibility with the application driver:

ALTER USER 'seafile'@'%' IDENTIFIED WITH mysql_native_password BY 'Password';
FLUSH PRIVILEGES;

2. Special Characters in Password

If your actual password contains special characters (like $, &, or *), Podman Quadlet files or the shell environment might be misinterpreting them. If this is the case, try wrapping the password in single quotes in your configuration or testing with a simple alphanumeric password temporarily to rule this out.

Please check the logs again after these changes and let me know if the “Access denied” message has changed.

Dear Daniel,

I still get the following error

2026-06-30 16:58:37 17785 [Warning] Access denied for user 'seafile'@'10.88.0.1' (using password: YES)
2026-06-30 16:59:16 17790 [Warning] Aborted connection 17790 to db: 'seahub_db' user: 'seafile' host: '127.0.0.1' (Got an error reading communication packets)
2026-06-30 16:59:16 17791 [Warning] Aborted connection 17791 to db: 'seahub_db' user: 'seafile' host: '127.0.0.1' (Got an error reading communication packets)
2026-06-30 16:59:41 17793 [Warning] Aborted connection 17793 to db: 'seafile_db' user: 'seafile' host: '127.0.0.1' (Got an error reading communication packets)
2026-06-30 18:06:48 18223 [Warning] Access denied for user 'seafile'@'10.88.0.1' (using password: YES)
2026-06-30 18:08:32 18239 [Warning] Access denied for user 'seafile'@'10.88.0.1' (using password: YES)

I checked for the type of password and it seems to be mysql_native_password

SELECT user, host, plugin FROM mysql.user WHERE user = 'seafile';
+---------+------+-----------------------+
| User    | Host | plugin                |
+---------+------+-----------------------+
| seafile | %    | mysql_native_password |
+---------+------+-----------------------+
1 row in set (0.041 sec)

My password has special characters and I now used the ’ ’ and I see that the container has the right password and it also had the correct password before.

Is there maybe some error in my Caddyfile for the reverse proxy which messes up something?

seafile.domain.ch {
    handle /socket.io/* {
        reverse_proxy 10.10.0.2:6232 {
            header_up Connection {http.request.header.Connection}
            header_up Upgrade {http.request.header.Upgrade}
        }
    }

    handle /sdoc-server/* {
        uri strip_prefix /sdoc-server
        reverse_proxy 10.10.0.2:6232
    }

    handle /seafdav/* {
        reverse_proxy 10.10.0.2:6233
    }

    handle /seafhttp/* {
        uri strip_prefix /seafhttp
        reverse_proxy 10.10.0.2:8082
    }

    handle /notification/* {
        reverse_proxy 10.10.0.2:6234 {
            header_up Connection {http.request.header.Connection}
            header_up Upgrade {http.request.header.Upgrade}
        }
    }

    reverse_proxy 10.10.0.2:8088 {
        header_up Host {host}
        header_up X-Real-IP {remote_host}
        header_up X-Forwarded-For {remote_host}
        header_up X-Forwarded-Proto {scheme}
	header_up X-Forwarded-Host {host}
    }
}

It seems there is a progress. Now the error is password incorrect instead of no password.

The “Access denied” error in the MariaDB logs confirms that the SeaDoc container is reaching the database, but the password being sent is not what MariaDB expects.

Based on your description, there are two likely causes related to how Podman and systemd handle environment variables:

1. Curly Quotes (Smart Quotes)

You mentioned using ’ ’ to wrap your password. In systemd unit files and Quadlet configurations, curly quotes are not recognized as quoting characters. They will be treated as literal parts of the password. For example, if your password is Secret123, using ’Secret123’ results in the container seeing the password as ’Secret123’ (including the quotes).

If you must use quotes, use standard straight quotes (' ' or " "), though usually, quotes are not needed in Environment= lines unless there is leading/trailing whitespace.

2. Escaping Special Characters

If your password contains a percent sign (%), systemd requires it to be escaped as %%. This is because systemd uses % for specifier expansion (e.g., %u for user). If you have a % in your Quadlet Environment= line and haven’t escaped it, the container will receive a truncated or modified string.

3. Caddyfile and Reverse Proxy

To answer your question: No, the Caddyfile is not the cause. The “Access denied” error happens during an internal connection between the SeaDoc container and the MariaDB container. The reverse proxy only handles external HTTP traffic reaching the SeaDoc web server; it has no involvement in how SeaDoc talks to the database.

Recommended Test

To isolate the issue, I suggest the following:

  1. Temporarily change the MariaDB password for the seafile user to a simple alphanumeric string (e.g., SeafileTest123).
  2. Update your SeaDoc Quadlet with this simple password (without any quotes).
  3. Run systemctl --user daemon-reload and restart the service.

If this works, you’ll know for sure it was an escaping/quoting issue. You can then try to re-introduce your complex password using standard straight quotes and escaping any % as %%.

Dear Daniel,

I removed data I had and replaces all the password by some long passwords without special symbols and restartet all the container. Now the seadoc container works and I can open sdoc files!

Thank you very much for your help and I will post my current setup in case someone else wants to do the same thing.


#### Pod file
[Pod]
PodName=seafile
PublishPort=8088:80
PublishPort=6233:8080
PublishPort=6234:8083
PublishPort=8082:8082
PublishPort=3306:3306
PublishPort=6379:6379


[Service]
Restart=always

[Install]
WantedBy=default.target





#### Environment file
# ─── Allgemein ───────────────────────────────────────────────────────────────
TIME_ZONE=Europe/Zurich

SEAFILE_SERVER_HOSTNAME=seafile.domain.ch
SEAFILE_SERVER_PROTOCOL=https

# Zwingend erforderlich – wird intern von Seafile/Notification-Server gebraucht.
# Generieren: openssl rand -hex 32
JWT_PRIVATE_KEY=key


# ─── Datenbank ────────────────────────────────────────────────────────────────
# Im Pod kommunizieren alle Container über localhost
SEAFILE_MYSQL_DB_HOST=127.0.0.1
SEAFILE_MYSQL_DB_PORT=3306
SEAFILE_MYSQL_DB_USER=seafile
SEAFILE_MYSQL_DB_PASSWORD=user-password

# Nur beim allerersten Start benötigt:
INIT_SEAFILE_MYSQL_ROOT_PASSWORD=root-password

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 (Redis) ────────────────────────────────────────────────────────────
CACHE_PROVIDER=redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379

# ─── Admin-Account (nur Erstinstallation) ─────────────────────────────────────
INIT_SEAFILE_ADMIN_EMAIL=name@protonmail.com
INIT_SEAFILE_ADMIN_PASSWORD=admin-password

# ─── SeaDoc ───────────────────────────────────────────────────────────────────
ENABLE_SEADOC=true
SEADOC_SERVER_URL=https://seafile.domain.ch/sdoc-server
SEADOC_VOLUME=/mnt/storage/seafile/sdoc





#### Seafile container
[Unit]
Description=Seafile Server (Community Edition)
After=seafile-db.service seafile-redis.service
Requires=seafile-pod.service seafile-db.service seafile-redis.service

[Container]
ContainerName=seafile-server
Image=docker.io/seafileltd/seafile-mc:13.0-latest
Pod=seafile.pod

Label=io.containers.autoupdate=registry

EnvironmentFile=/etc/containers/systemd/seafile.env

Volume=/mnt/storage/seafile/data:/shared:Z

[Service]
Restart=on-failure
TimeoutStartSec=300

[Install]
WantedBy=default.target





#### Seadoc container
[Unit]
Description=Seafile SeaDoc Server

[Container]
ContainerName=seafile-seadoc
Image=docker.io/seafileltd/sdoc-server:2.0-latest
PublishPort=6232:7070

Label=io.containers.autoupdate=registry

# EnvironmentFile=/etc/containers/systemd/seafile.env

Environment=DB_HOST=10.88.0.1
Environment=DB_PORT=3306
Environment=DB_USER=seafile
Environment=DB_PASSWORD=user-password
Environment=DB_NAME=seahub_db
# Environment=REDIS_HOST=10.88.0.1
Environment=JWT_PRIVATE_KEY=key
# Environment=NON_ROOT=false

Environment=SEAHUB_SERVICE_URL=https://seafile.domain.ch

Volume=/mnt/storage/seafile/sdoc:/shared:Z

[Service]
Restart=on-failure
TimeoutStartSec=120

[Install]
WantedBy=default.target





#### Redis container
[Unit]
Description=Seafile Redis Cache
After=seafile-pod.service
Requires=seafile-pod.service

[Container]
ContainerName=seafile-redis
Image=docker.io/redis:7-alpine
Pod=seafile.pod

Label=io.containers.autoupdate=registry

HealthCmd=redis-cli ping || exit 1
HealthInterval=10s
HealthRetries=5
Notify=healthy

[Service]
Restart=on-failure

[Install]
WantedBy=default.target





#### notification container
[Unit]
Description=Seafile Notification Server
After=seafile-server.service
Requires=seafile-pod.service

[Container]
ContainerName=seafile-notification
Image=docker.io/seafileltd/notification-server:13.0-latest
Pod=seafile.pod

Label=io.containers.autoupdate=registry

EnvironmentFile=/etc/containers/systemd/seafile.env

Volume=/mnt/storage/seafile/data/seafile/logs:/shared/seafile/logs:Z

[Service]
Restart=on-failure

[Install]
WantedBy=default.target





#### Mariadb container
[Unit]
Description=Seafile MariaDB Database
After=seafile-pod.service
Requires=seafile-pod.service

[Container]
ContainerName=seafile-db
Image=docker.io/mariadb:lts
Pod=seafile.pod

Label=io.containers.autoupdate=registry

Volume=/mnt/storage/seafile/db:/var/lib/mysql:Z

EnvironmentFile=/etc/containers/systemd/seafile.env
Environment=MARIADB_ROOT_PASSWORD=user-password
Environment=MYSQL_INITDB_SKIP_TZINFO=1
Environment=MYSQL_LOG_CONSOLE=true
Environment=MARIADB_AUTO_UPGRADE=1

HealthCmd=healthcheck.sh --connect --innodb_initialized
HealthInterval=20s
HealthRetries=10
HealthTimeout=5s
HealthStartPeriod=30s
Notify=healthy

[Service]
Restart=on-failure

[Install]
WantedBy=default.target





#### Caddyfile
seafile.domain.ch {
    handle /socket.io/* {
        reverse_proxy 10.10.0.2:6232 {
            header_up Connection {http.request.header.Connection}
            header_up Upgrade {http.request.header.Upgrade}
        }
    }

    handle /sdoc-server/* {
        uri strip_prefix /sdoc-server
        reverse_proxy 10.10.0.2:6232
    }

    handle /seafdav/* {
        reverse_proxy 10.10.0.2:6233
    }

    handle /seafhttp/* {
        uri strip_prefix /seafhttp
        reverse_proxy 10.10.0.2:8082
    }

    handle /notification/* {
        reverse_proxy 10.10.0.2:6234 {
            header_up Connection {http.request.header.Connection}
            header_up Upgrade {http.request.header.Upgrade}
        }
    }

    reverse_proxy 10.10.0.2:8088 {
        header_up Host {host}
        header_up X-Real-IP {remote_host}
        header_up X-Forwarded-For {remote_host}
        header_up X-Forwarded-Proto {scheme}
	header_up X-Forwarded-Host {host}
    }
}

Thanks for your time!!!