Containerized Caddy Reverse Proxy to Seafile throws error

1. The problem I’m having:

I’m setting up Caddy as a reverse proxy to my Seafile container. Everything was working correctly until I added Caddy, so I’m sure it’s something in my config files that aren’t correct. I’ve combed the internet looking for solutions and nothing seems to fix the error shown below. It would appear that Caddy isn’t able to proxy to the seafile container, why at this point I have no idea.

2. Error messages and/or full log output:

caddy_1  | {"level":"error","ts":1718826289.8752694,"logger":"http.log.error","msg":"dial tcp 172.19.0.6:8080: connect: connection refused","request":{"remote_ip":"71.88.214.165","remote_port":"51737","client_ip":"71.88.214.165","proto":"HTTP/3.0","method":"GET","host":"docs.eaglewings.com","uri":"/","headers":{"Sec-Ch-Ua":["\"Not/A)Brand\";v=\"8\", \"Chromium\";v=\"126\", \"Brave\";v=\"126\""],"Upgrade-Insecure-Requests":["1"],"Sec-Fetch-Site":["cross-site"],"Sec-Fetch-Dest":["document"],"Sec-Gpc":["1"],"Cache-Control":["max-age=0"],"Sec-Ch-Ua-Platform":["\"Linux\""],"Sec-Fetch-Mode":["navigate"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Priority":["u=0, i"],"Sec-Ch-Ua-Mobile":["?0"],"User-Agent":["Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8"],"Accept-Language":["en-US,en;q=0.8"],"Sec-Fetch-User":["?1"]},"tls":{"resumed":true,"version":772,"cipher_suite":4865,"proto":"h3","server_name":"docs.eaglewings.com"}},"duration":0.001601044,"status":502,"err_id":"bghv089aa","err_trace":"reverseproxy.statusError (reverseproxy.go:1248)"}

3. Caddy version:

v2.7.4

4. How I installed and ran Caddy:

Install is via docker compose files

a. System environment:

Docker

b. Command:

Caddy Loads when the container runs

c. Service/unit/compose file:

version: "3"

networks:
    docker-network:
        driver: bridge

services:
    caddy:
        image: caddy:latest
        volumes:
            - ./Caddyfile:/etc/caddy/Caddyfile
            - ./data/caddy:/data
            - ./caddy_config:/config
        ports:
            - "80:80"
            - "443:443"
            - "443:443/udp"
        restart: unless-stopped
        networks:
            - docker-network

    seafile:
        image: seafileltd/seafile:latest
        volumes:
            - ./data/seafile:/shared
        expose:
            - 8000
            - 8082
            - 8080
        restart: unless-stopped
        environment:
            - SEAFILE_ADMIN_EMAIL=${SEAFILE_ADMIN_EMAIL} # Specifies Seafile admin user, default is 'me@example.com'.
            - SEAFILE_ADMIN_PASSWORD=${SEAFILE_PASSWORD} # Specifies Seafile admin password, default is 'asecret'.
            - SEAFILE_SERVER_LETSENCRYPT=false # Whether to use https or not.

            - SEAFILE_SERVER_HOSTNAME=docs.${DOMAIN_NAME}
            - SEAFILE_SERVICE_URL=https://docs.${DOMAIN_NAME}
            - SEAFILE_FILE_SERVER_ROOT=https://docs.${DOMAIN_NAME}/seafhttp

            - DB_HOST=seafile-db
            - DB_PORT=3306
            - MYSQL_HOST=seafile-db
            - MYSQL_PORT=3306
            - MYSQL_ROOT_PASSWORD=${SEAFILE_DB_PASSWORD}
            - TIME_ZONE=Est/EST
        networks:
            - docker-network

    seafile-db:
        image: mariadb:latest
        volumes:
            - ./data/seafile-db:/var/lib/mysql
        restart: unless-stopped
        environment:
            - MYSQL_ROOT_PASSWORD=${SEAFILE_DB_PASSWORD}
        networks:
            - docker-network

    trade-journal:
        image: ghost:latest
        volumes:
            - ./data/trade-journal:/var/lib/ghost/content
        restart: always
        ports:
            - 2368:2368
        environment:
            - database__client=mysql
            - database__connection__host=trade-journal-db
            - database__connection__user=trade-journal
            - database__connection__password=${TRADE_JOURNAL_DB_PASSWORD}
            - database__connection__database=trade-journal
            - url=https://tradejournal.${DOMAIN_NAME}
        networks:
            - docker-network

    trade-journal-db:
        image: mariadb:latest
        volumes:
            - ./data/trade-journal-db:/var/lib/mysql
        restart: unless-stopped
        environment:
            - MYSQL_ROOT_PASSWORD=${TRADE_JOURNAL_DB_PASSWORD}
            - MYSQL_DATABASE=trade-journal
            - MYSQL_USER=trade-journal
            - MYSQL_PASSWORD=${TRADE_JOURNAL_DB_PASSWORD}
        networks:
            - docker-network

volumes:
    caddy_config:

d. My complete Caddy config:

{
	debug
	# acme_ca https:#acme.zerossl.com/v2/DV90
	email _@eaglewings.com
}

docs.eaglewings.com {
	reverse_proxy seafile:8000
	tls _@eaglewings.com
}

tradejournal.eaglewings.com {
	reverse_proxy trade-journal:2368
	tls _@eaglewings.com
}

5. Links to relevant resources:

Are you sure port 8000 is the correct port? From a quick google search, I think it uses port 80 (i.e. the default HTTP port).

1 Like

I’m not sure that seafile uses 8000, but when I put in port 80, I get an error that 80 is already in use. I tried mapping 8000 to 80 or even using 8080 to 80 and I was getting the same errors.

You don’t need to map any ports. Just try proxying to port 80.

1 Like

I did try port 80 and an error was thrown that 80 was already in use. Thus the reason I thought a map might work.

Can you post that error here verbatim?

Port 80 can’t already be in use in the Seafile container because Seafile is the only thing that listens on port 80 in the Seafile container.

1 Like

The error is as follows:

ERROR: for docker_seafile_1  Cannot start service seafile: driver failed programming external connectivity on endpoint docker_seafile_1 (e9554651282cac84a28e2981d605232bbdfb4bb84e6a8b63731e558a211a5d08): Bind for 0.0.0.0:80 failed: port is already allocated

And I updated my docker file for the seafile container as follows: NOTE I added the port section to the seafile container.

seafile:
        image: seafileltd/seafile:latest
        volumes:
            - ./data/seafile:/shared
        ports:
            - 80:80
        restart: unless-stopped
        environment:
            - SEAFILE_ADMIN_EMAIL=${SEAFILE_ADMIN_EMAIL} # Specifies Seafile admin user, default is 'me@example.com'.
            - SEAFILE_ADMIN_PASSWORD=${SEAFILE_PASSWORD} # Specifies Seafile admin password, default is 'asecret'.
            - SEAFILE_SERVER_LETSENCRYPT=false # Whether to use https or not.

            - SEAFILE_SERVER_HOSTNAME=docs.${DOMAIN_NAME}
            - SEAFILE_SERVICE_URL=https://docs.${DOMAIN_NAME}
            - SEAFILE_FILE_SERVER_ROOT=https://docs.${DOMAIN_NAME}/seafhttp

            - DB_HOST=seafile-db
            - DB_PORT=3306
            - MYSQL_HOST=seafile-db
            - MYSQL_PORT=3306
            - MYSQL_ROOT_PASSWORD=${SEAFILE_DB_PASSWORD}
            - TIME_ZONE=Est/EST
        networks:
            - docker-network

This is a Docker misconfiguration issue.

driver failed programming external connectivity and Bind for 0.0.0.0:80 failed: port is already allocated mean that Docker tried to allocate the port on the host to pass through to the container and failed because the port is already in use.

To be clear, this is an error we should expect for the Seafile container - because this container should not be binding the host port 80. Instead, Caddy should bind host port 80 and proxy to the Seafile container over the internal Docker compose network.

Remove the ports: stanza from the seafile container and start it again; this key is used only for binding host ports and shouldn’t have been specified for your Seafile deployment. Update your Caddyfile to point to seafile (no explicit port/port 80).

1 Like

Thank you so much for the directions on the docker issue. The container now loads, but the web ui for seafile still returns a 502 error. The Caddy error is as follows, but it would seem like the seafile interface web server might not be working.

caddy_1  | {"level":"error","ts":1719194043.894804,"logger":"http.log.error","msg":"dial tcp: lookup seafile on 127.0.0.11:53: server misbehaving","request":{"remote_ip":"71.88.214.165","remote_port":"55637","client_ip":"71.88.214.165","proto":"HTTP/3.0","method":"GET","host":"docs.eaglewings.com","uri":"/","headers":{"Accept-Language":["en-US,en;q=0.5"],"Sec-Fetch-Mode":["navigate"],"Sec-Fetch-Dest":["document"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Upgrade-Insecure-Requests":["1"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8"],"Priority":["u=0, i"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["none"],"Sec-Gpc":["1"],"Sec-Ch-Ua-Platform":["\"Linux\""],"User-Agent":["Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"],"Sec-Ch-Ua":["\"Not/A)Brand\";v=\"8\", \"Chromium\";v=\"126\", \"Brave\";v=\"126\""],"Sec-Fetch-User":["?1"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h3","server_name":"docs.eaglewings.com"}},"duration":0.008435209,"status":502,"err_id":"0u75brfwt","err_trace":"reverseproxy.statusError (reverseproxy.go:1248)"}

dial tcp: lookup seafile on 127.0.0.11:53: server misbehaving

One might assume that this indicates the Docker DNS resolver is having issues, but looking into it, it seems like this is the result we see any time it can’t resolve a given DNS name. So it’s possible that DNS is broken, but it’s also possible that something is simply misspelled somewhere, or the containers aren’t properly configured on the same network, or maybe the seafile container isn’t currently running?

This will require more troubleshooting on the Docker side of things, I’m afraid. Caddy is probably working fine, but there’s nothing it can do if Docker can’t give it an IP address to connect to for the seafile container.

1 Like

This is what I was concerned about. Thank you so very much for your help!! I’m glad Caddy is working as expected.

1 Like

I’ve been working this issue and am still baffled that Caddy is throwing an error looking for the server on 127.0.0.1:53, the port is the baffling part. SeaFile does have an nginx server that runs on 127.0.0.1 but on ports 8000, 8080, 8082 but nothing on 53. So where is the 53 coming from?

This is the seafile.nginx.conf file:

server {
listen 80;
server_name docs.eaglewings.com;

    client_max_body_size 10m;

    location / {
        proxy_pass http://127.0.0.1:8000/;
        proxy_read_timeout 310s;
        proxy_set_header Host $host;
        proxy_set_header Forwarded "for=$remote_addr;proto=$scheme";
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Connection "";
        proxy_http_version 1.1;

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

    location /seafhttp {
        rewrite ^/seafhttp(.*)$ $1 break;
        proxy_pass http://127.0.0.1:8082;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        client_max_body_size 0;
        proxy_connect_timeout  36000s;
        proxy_read_timeout  36000s;
        proxy_request_buffering off;
        access_log      /var/log/nginx/seafhttp.access.log seafileformat;
        error_log       /var/log/nginx/seafhttp.error.log;
    }

    location /notification/ping {
        proxy_pass http://127.0.0.1:8083/ping;
        access_log      /var/log/nginx/notification.access.log seafileformat;
        error_log       /var/log/nginx/notification.error.log;
    }

    location /notification {
        proxy_pass http://127.0.0.1:8083/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        access_log      /var/log/nginx/notification.access.log seafileformat;
        error_log       /var/log/nginx/notification.error.log;
    }

    location /seafdav {
        proxy_pass         http://127.0.0.1:8080;
        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;
        proxy_set_header   X-Forwarded-Proto $scheme;
        proxy_read_timeout  1200s;
        client_max_body_size 0;

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

    location /media {
        root /opt/seafile/seafile-server-latest/seahub;
    }

}

Port 53 is DNS. This is Caddy saying “I tried to resolve a hostname using the system’s DNS resolver, but I got an error”.

In other words, Caddy reached out to Docker’s DNS resolver, but it said “:man_shrugging: I dunno what seafile is”.

I don’t know what’s going wrong here, because you seem to have both your caddy and seafile services in the same Docker network (unless you changed that since your original post, or have somekind of other weird Docker stuff going on that you didn’t share).

1 Like

This is a weird error for sure. Thank you for explaining the error 53. The odd thing is that I have two other ghost instances running, in the same network as the seafile container and everything seems fine.

Seafile spins up a nginx proxy as well that listens on port 80. Maybe there’s some weirdness going on there with conflicting issues between Caddy and Seafile.

I was thinking of removing everything to do with Seafile, but I’d hate to lose all the docs that have been stored on the server.

First thing I’d probably try is spin the whole compose project down, delete all the involved networks, and spin it all back up again.

There might be some kind of gremlin involved with the DNS resolution for the compose bridge network that a rebuild will solve.

1 Like

I tried this suggestion and removed all my containers and networks, restarted the machine and then re-ran the docker compose command and everything came back online. BUT i’m still getting the error I mentioned earlier. In fact I added a couple of containers, one for Memos, and two for HedgeDoc and the Memos container works just fine, but the HedgeDoc is throwing the same error that the SeaFile container is, which I thought was really interesting.

Here’s my updated compose file:

version: "3"

networks:
    docker-network:
        driver: bridge

services:
    caddy:
        image: caddy:latest
        container_name: caddy
        volumes:
            - ./Caddyfile:/etc/caddy/Caddyfile
            - ./data/caddy:/data
            - ./caddy_config:/config
            - media:/shared/media
            - ./seafile/seahub-data:/shared/seahub-data
        ports:
            - "80:80"
            - "443:443"
            - "443:443/udp"
        restart: unless-stopped
        networks:
            - docker-network

    memcached:
        image: memcached:latest
        container_name: seafile-memcached
        entrypoint: memcached -m 256
        restart: unless-stopped
        networks:
            - docker-network

    seafile:
        image: seafileltd/seafile-mc:latest
        container_name: seafile
        ports:
            - 3001:80
        volumes:
            - /opt/seafile-data:/shared 
        restart: unless-stopped
        environment:
            - SEAFILE_ADMIN_EMAIL=${SEAFILE_ADMIN_EMAIL} # Specifies Seafile admin user, default is 'me@example.com'.
            - SEAFILE_ADMIN_PASSWORD=${SEAFILE_PASSWORD} # Specifies Seafile admin password, default is 'asecret'.
            - SEAFILE_SERVER_LETSENCRYPT=false # Whether to use https or not.

            - SEAFILE_SERVER_HOSTNAME=docs.${DOMAIN_NAME}
            - SEAFILE_SERVICE_URL=https://docs.${DOMAIN_NAME}
            - SEAFILE_FILE_SERVER_ROOT=https://docs.${DOMAIN_NAME}/seafhttp

            - DB_HOST=seafile-db
            - DB_PORT=3306
            - MYSQL_HOST=seafile-db
            - MYSQL_PORT=3306
            - MYSQL_ROOT_PASSWORD=${SEAFILE_DB_PASSWORD}
            - TIME_ZONE=Est/EST
        depends_on:
            - seafile-db
            - memcached
        networks:
            - docker-network

    seafile-db:
        image: mariadb:latest
        container_name: seafile-db
        volumes:
            - ./data/seafile-db:/var/lib/mysql
        restart: unless-stopped
        environment:
            - MYSQL_ROOT_PASSWORD=${SEAFILE_DB_PASSWORD}
        networks:
            - docker-network

    trade-journal:
        image: ghost:latest
        container_name: trade-journal-ghost
        volumes:
            - ./data/trade-journal:/var/lib/ghost/content
        restart: always
        ports:
            - 2368:2368
        environment:
            - database__client=mysql
            - database__connection__host=trade-journal-db
            - database__connection__user=trade-journal
            - database__connection__password=${TRADE_JOURNAL_DB_PASSWORD}
            - database__connection__database=trade-journal
            - url=https://tradejournal.${DOMAIN_NAME}
        networks:
            - docker-network

    trade-journal-db:
        image: mariadb:latest
        container_name: trade-journal-ghost-db
        volumes:
            - ./data/trade-journal-db:/var/lib/mysql
        restart: unless-stopped
        environment:
            - MYSQL_ROOT_PASSWORD=${TRADE_JOURNAL_DB_PASSWORD}
            - MYSQL_DATABASE=trade-journal
            - MYSQL_USER=trade-journal
            - MYSQL_PASSWORD=${TRADE_JOURNAL_DB_PASSWORD}
        networks:
            - docker-network

    eeh:
        image: ghost:latest
        container_name: eeh-ghost
        volumes:
            - ./data/eeh:/var/lib/ghost/content
        restart: always
        ports:
            - 2468:2468
        environment:
            - database__client=mysql
            - database__connection__host=eeh-db
            - database__connection__user=eeh
            - database__connection__password=${EEH_DB_PASSWORD}
            - database__connection__database=eeh
            - url=https://${DOMAIN_NAME2}
        networks:
            - docker-network

    eeh-db:
        image: mariadb:latest
        container_name: eeh-ghost-db
        volumes:
            - ./data/eeh-db:/var/lib/mysql
        restart: unless-stopped
        environment:
            - MYSQL_ROOT_PASSWORD=${EEH_DB_PASSWORD}
            - MYSQL_DATABASE=eeh
            - MYSQL_USER=eeh
            - MYSQL_PASSWORD=${EEH_DB_PASSWORD}
        networks:
            - docker-network

    memos:
        image: neosmemo/memos:stable
        container_name: memos
        volumes:
            - ~/.memos/:/var/opt/memos
        ports:
            - 5230:5230
        networks:
            - docker-network

    hedgedoc-db:
        image: postgres:13.4-alpine
        container_name: hedgedoc-db
        environment:
            - POSTGRES_USER=${HEDGEDOC_DB_ADMIN}
            - POSTGRES_PASSWORD=${HEDGEDOC_DB_PASSWORD}
            - POSTGRES_DB=hedgedoc
        volumes:
            - ./data/hedgedoc/database:/var/lib/postgresql/data
        restart: always
        # networks:
        #     - docker-network

    hedgedoc:
        # Make sure to use the latest release from https://hedgedoc.org/latest-release
        image: quay.io/hedgedoc/hedgedoc:1.9.9
        container_name: hedgedoc
        environment:
            - CMD_DB_URL=postgres://${HEDGEDOC_DB_ADMIN}:${HEDGEDOC_DB_PASSWORD}@database:5432/hedgedoc
            - CMD_DOMAIN=hedgedoc.eaglewings.com
            - CMD_URL_ADDPORT=true
        volumes:
            - ./data/hedgedoc/uploads:/hedgedoc/public/uploads
        ports:
            - "3000:3000"
        restart: always
        depends_on:
            - hedgedoc-db
        # networks:
        #     - docker-network

volumes:
    caddy_config:
    media:

And here is my updated CaddyFile:

{
	# acme_ca https:#acme.zerossl.com/v2/DV90
	email admin@eaglewings.com
}

docs.eaglewings.com {
	reverse_proxy seafile:3001
	handle /seafhttp* {
		reverse_proxy seafile:8082
	}

	handle_path /media* {
		root * /shared/media
		file_server
	}
}

tradejournal.eaglewings.com {
	reverse_proxy trade-journal:2368
	tls admin@eaglewings.com
}

memos.eaglewings.com {
	reverse_proxy memos:5230
}

hedgedoc.eaglewings.com {
	@websockets {
          header Connection *Upgrade*
          header Upgrade websocket
          path /socket.io/*
        }

	reverse_proxy hedgedoc:3000
}

eaglewingsessentialhealth.com {
	reverse_proxy eeh:2368
	tls admin@eaglewings.com
}

Like I said, it’s a port number, not an error code. E.g. the HTTP port is 80, the HTTPS port is 443, and the DNS port is 53.

1 Like

What I find odd is that Caddy can’t resolve or maybe it’s a docker problem, for hosting Seafile, as well as hedgedoc but Memos has no problems resolving.