SelfhostedPro / Yacht

A web interface for managing docker containers with an emphasis on templating to provide 1 click deployments. Think of it like a decentralized app store for servers that anyone can make packages for.
MIT License
3.32k stars 158 forks source link

[Bug Report] daemon doesn't listen on IPv6 #545

Closed mbunkus closed 4 weeks ago

mbunkus commented 1 year ago

Describe the bug The daemon only listens on IPv4, even if the Docker container has IPv6 enabled & working.

To Reproduce Steps to reproduce the behavior:

  1. Configure Docker to use IPv6
  2. Modify the docker-compose.yml file to enable IPv6 for the network (see below)
  3. Run docker compose up
  4. Run docker compose inspect … to determine IPv4 & IPv6 addresses
  5. Try to connect to the IPv4 & IPv6 addresses determined in step 4, port 8000 each time, & observer that you can connect via IPv4 but not IPv6:
[0 root@durnik /srv/docker-compose/yacht] docker container inspect yacht | rg 'IPAddress|GlobalIPv6Address' | tail -n 2
                    "IPAddress": "172.18.2.2",
                    "GlobalIPv6Address": "2a01:4f8:262:xxxx:a:b:c:d",
[0 root@durnik /srv/docker-compose/yacht] telnet 172.18.2.2 8000
Trying 172.18.2.2...
Connected to 172.18.2.2.
Escape character is '^]'.
HTTP/1.1 400 Bad Request
Server: nginx/1.18.0
Date: Sun, 20 Nov 2022 00:38:37 GMT
Content-Type: text/html
Content-Length: 157
Connection: close

<html>
<head><title>400 Bad Request</title></head>
<body>
<center><h1>400 Bad Request</h1></center>
<hr><center>nginx/1.18.0</center>
</body>
</html>
Connection closed by foreign host.
[1 root@durnik /srv/docker-compose/yacht] telnet 2a01:4f8:262:xxxx:a:b:c:d 8000
Trying 2a01:4f8:262:xxxx:a:b:c:d...
telnet: Unable to connect to remote host: Connection refused

Actual IPv6 obscured.

Expected behavior The daemon should listen on both IPv4 & IPv6.

Desktop (please complete the following information):

Additional context

Contents of docker-compose.yml:

---
version: "3"
services:
  yacht:
    container_name: yacht
    restart: unless-stopped
    ports:
      - 8200:8000
    volumes:
      - /srv/docker-volumes/yacht-config:/config
      - /var/run/docker.sock:/var/run/docker.sock
    image: selfhostedpro/yacht
    networks:
      - net

networks:
  net:
    enable_ipv6: true
    ipam:
      config:
        - subnet: "172.18.2.0/24"
        - subnet: "2a01:4f8:262:xxxx:a::/80"

Again, actual IPv6 subnet obscured.

Logs

[0 root@durnik /srv/docker-compose/yacht] docker compose logs
yacht  | [s6-init] making user provided files available at /var/run/s6/etc...exited 0.
yacht  | [s6-init] ensuring user provided files have correct perms...exited 0.
yacht  | [fix-attrs.d] applying ownership & permissions fixes...
yacht  | [fix-attrs.d] done.
yacht  | [cont-init.d] executing container initialization scripts...
yacht  | [cont-init.d] 01-envfile: executing...
yacht  | [cont-init.d] 01-envfile: exited 0.
yacht  | [cont-init.d] 02-tamper-check: executing...
yacht  | [cont-init.d] 02-tamper-check: exited 0.
yacht  | [cont-init.d] 10-adduser: executing...
yacht  | usermod: no changes
yacht  |
yacht  | -------------------------------------
yacht  |           _         ()
yacht  |          | |  ___   _    __
yacht  |          | | / __| | |  /  \
yacht  |          | | \__ \ | | | () |
yacht  |          |_| |___/ |_|  \__/
yacht  |
yacht  |
yacht  | Brought to you by linuxserver.io
yacht  | -------------------------------------
yacht  |
yacht  | To support LSIO projects visit:
yacht  | https://www.linuxserver.io/donate/
yacht  | -------------------------------------
yacht  | GID/UID
yacht  | -------------------------------------
yacht  |
yacht  | User uid:    911
yacht  | User gid:    911
yacht  | -------------------------------------
yacht  |
yacht  | [cont-init.d] 10-adduser: exited 0.
yacht  | [cont-init.d] 30-config: executing...
yacht  | [cont-init.d] 30-config: exited 0.
yacht  | [cont-init.d] 31-migrate: executing...
yacht  | INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
yacht  | INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
yacht  | INFO  [alembic.autogenerate.compare] Detected removed index 'ix_secret_key_key' on 'secret_key'
yacht  | INFO  [alembic.autogenerate.compare] Detected removed table 'secret_key'
yacht  | INFO  [alembic.autogenerate.compare] Detected removed index 'ix_jwt_token_blacklist_jti' on 'jwt_token_blacklist'
yacht  | INFO  [alembic.autogenerate.compare] Detected removed table 'jwt_token_blacklist'
yacht  | --- MODELS ---
yacht  | Generating /alembic/versions/9afeebe2e0e4_automated_db_upgrade.py ...  done
yacht  | INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
yacht  | INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
yacht  | INFO  [alembic.runtime.migration] Running upgrade  -> 9afeebe2e0e4, automated db upgrade
yacht  | --- MODELS ---
yacht  | [cont-init.d] 31-migrate: exited 0.
yacht  | [cont-init.d] 32-env: executing...
yacht  | Replacing env constants in JS
yacht  | Processing /app/js/app.37804ef8.js ...
yacht  | Processing /app/js/app.37804ef8.js.map ...
yacht  | Processing /app/index.html ...
yacht  | [cont-init.d] 32-env: exited 0.
yacht  | [cont-init.d] 90-custom-folders: executing...
yacht  | [cont-init.d] 90-custom-folders: exited 0.
yacht  | [cont-init.d] 99-custom-files: executing...
yacht  | [custom-init] no custom files found exiting...
yacht  | [cont-init.d] 99-custom-files: exited 0.
yacht  | [cont-init.d] done.
yacht  | [services.d] starting services
yacht  | [services.d] done.
yacht  | INFO:     Started server process [356]
yacht  | INFO:     Waiting for application startup.
yacht  | INFO:     Application startup complete.
yacht  | INFO:     Uvicorn running on unix socket /tmp/gunicorn.sock (Press CTRL+C to quit)
yacht  | Secret key generated
yacht  | Secret key exists
yacht  | DISABLE_AUTH = False (<class 'bool'>)
yacht  | Users Exist
yacht  | Template Variables Exist
wickedyoda commented 1 year ago

I believe the container is setup to support ipv4 only, in general docker does not work well with ip6.

mbunkus commented 1 year ago

The container has no problem with IPv6, or rather, with outgoing IPv6:

[0 root@durnik /srv/docker-compose/yacht] docker exec -ti yacht /bin/bash
root@23d4d0143f51:/api# ping -6 www.google.com
PING www.google.com (2a00:1450:4001:802::2004): 56 data bytes
64 bytes from 2a00:1450:4001:802::2004: seq=0 ttl=116 time=5.407 ms
64 bytes from 2a00:1450:4001:802::2004: seq=1 ttl=116 time=5.852 ms
^C
--- www.google.com ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 5.407/5.629/5.852 ms
root@23d4d0143f51:/api#

I'm well aware of Docker's sub-par IPv6 support. Having images support IPv6 would be a great step in improving the situation overall, especially given that there are other orchestrators out there that use the same images. For example Kubernetes has proper IPv6 support now (or is pretty close to having it). And yes, I'm also aware that Kubernetes isn't a great argument in the context of Yacht :grin:

Anyway, in the case of Yacht "proper IPv6" support would probably mean nothing more than making the daemon listen on IPv6, too. In terms of sockets this means listening on :: instead of 0.0.0.0, or if the port number is part of the listen spec, on [::]:8000 instead of 0.0.0.0:8000. The reason is that at least on Linux listening on the IPv6 wildcard address will automatically listen on IPv4, too.

If you're worried about that, you can also try listening on both [::]:8000 and 0.0.0.0:8000, in that order, and ignore failures if exactly one of those fails:

As a last resort one might make the address/port number to listen on configurable via environment variables. Other images go that route.

As an example for Docker images that support listening on IPv6 out of the box (or where I could configure it via an environment variable): Authentik; Wekan; PowerDNS Admin; Docker Registry; Portainer.