jellyfin / jellyfin-vue

A modern web client for Jellyfin based on Vue
https://jellyfin.org
GNU General Public License v3.0
1.29k stars 229 forks source link

Docker bind() to 0.0.0.0:80 failed (13: Permission denied) #2236

Closed vlastaw closed 8 months ago

vlastaw commented 8 months ago

Description of the bug

Docker on Container station on QNAP QTS won't run the container. Throws error:

2024/02/25 00:32:26 [emerg] 1#1: bind() to 0.0.0.0:80 failed (13: Permission denied) nginx: [emerg] bind() to 0.0.0.0:80 failed (13: Permission denied)

This probably is an issue with port 80 and default Linux handling of privileged ports (<1024). I would suggest to set another default port for the app (>1024).

Setting the container to run in Privileged mode does not solve the issue. Even though I would not like to run it that way anyway.

My environment is otherwise OK - I run Feishin in Docker too.

Thanks for any solution.

Steps to reproduce

Import container (NAS virtual network), map ports (in my case 19181 > 80), try to run container.

Runs > throws error > container status "other" instead of running.

Expected behavior

Run.

Logs

2024/02/25 00:44:06 [emerg] 1#1: bind() to 0.0.0.0:80 failed (13: Permission denied)
nginx: [emerg] bind() to 0.0.0.0:80 failed (13: Permission denied)
==== Starting Jellyfin Vue setup ====

Writing data to /usr/share/nginx/html/config.json...
DEFAULT_SERVERS value: 
ALLOW_SERVER_SELECTION value: true
ROUTER_MODE value: history

====      Setup finished!        ====
--------------------------------------------------------------------------------------------------

{
AppArmorProfile:""
Args:[
"nginx"
"-g"
"daemon off;"
]
Config:{
AttachStderr:false
AttachStdin:false
AttachStdout:false
Cmd:[
"nginx"
"-g"
"daemon off;"
]
Domainname:""
Entrypoint:[
"/docker-entrypoint.sh"
]
Env:[
"NGINX_VERSION=1.24.0"
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
"PKG_RELEASE=1"
]
ExposedPorts:{
80/tcp:{}
}
Hostname:"e1d073263db9"
Image:"ghcr.io/jellyfin/jellyfin-vue:unstable"
Labels:{
maintainer:"Jellyfin Packaging Team - packaging@jellyfin.org"
org.opencontainers.image.description:"Commit: "
org.opencontainers.image.source:"https://github.com/jellyfin/jellyfin-vue"
}
OpenStdin:true
StdinOnce:false
StopSignal:"SIGQUIT"
Tty:true
User:"nginx"
WorkingDir:""
}
Created:"2024-02-25T00:43:48.890341186Z"
Driver:"overlay2"
GraphDriver:{
Data:{
LowerDir:"/share/CACHEDEV1_DATA/Container/container-station-data/lib/docker/overlay2/261029e3aaea5a9211abfd0cd02cd80d39b7c10c6a64792054c4148a971089bc-init/diff:/share/CACHEDEV1_DATA/Container/container-station-data/lib/docker/overlay2/25883ba6c6ac5bacf0259339a3a4f78dc2c2879f6eb3cb75a0ee84bebe527ce1/diff:/share/CACHEDEV1_DATA/Container/container-station-data/lib/docker/overlay2/c122ffbee0b6eeeb3b200fe7538e486bd13a4906f4dc01bd9ae45e7913ddcb36/diff:/share/CACHEDEV1_DATA/Container/container-station-data/lib/docker/overlay2/291e3df4ada8f0357a76b7794cfabda86fbd50a08d8379fe266286892af2a40d/diff:/share/CACHEDEV1_DATA/Container/container-station-data/lib/docker/overlay2/6ed2f4eaacc903b61a2f12006644a6e47564204dbb2348cb815bb12baa7abab0/diff:/share/CACHEDEV1_DATA/Container/container-station-data/lib/docker/overlay2/7bdb75087080008a1349578ce6f28c2b900fb7161e8d50b03c2d17fd6207c3e6/diff:/share/CACHEDEV1_DATA/Container/container-station-data/lib/docker/overlay2/7bdaf0b0bd5a11487d861888c870a18ec5914a7bfaea99ef7de3ff3d5d0af5b9/diff:/share/CACHEDEV1_DATA/Container/container-station-data/lib/docker/overlay2/f22c13638f9ac908c0325dd920256da7d29b1252cefffe1e4ec9962677b5001f/diff:/share/CACHEDEV1_DATA/Container/container-station-data/lib/docker/overlay2/e36ccf80d4c2b9c2e15896c05086d3663aec02ff1806dab97b5d53e3f544a965/diff:/share/CACHEDEV1_DATA/Container/container-station-data/lib/docker/overlay2/74e218aa97c787690eee60a8da76946657cebbb617e012092a6a20f21bb77810/diff:/share/CACHEDEV1_DATA/Container/container-station-data/lib/docker/overlay2/9194d0b2d3b9b7e0f1e2c2f00009613272cd695d388b5229f099a7549a5e06c2/diff:/share/CACHEDEV1_DATA/Container/container-station-data/lib/docker/overlay2/698335f672cbdb614ab38505610a628cd4068548139abe37ec05ea7d0261a3bb/diff"
MergedDir:"/share/CACHEDEV1_DATA/Container/container-station-data/lib/docker/overlay2/261029e3aaea5a9211abfd0cd02cd80d39b7c10c6a64792054c4148a971089bc/merged"
UpperDir:"/share/CACHEDEV1_DATA/Container/container-station-data/lib/docker/overlay2/261029e3aaea5a9211abfd0cd02cd80d39b7c10c6a64792054c4148a971089bc/diff"
WorkDir:"/share/CACHEDEV1_DATA/Container/container-station-data/lib/docker/overlay2/261029e3aaea5a9211abfd0cd02cd80d39b7c10c6a64792054c4148a971089bc/work"
}
Name:"overlay2"
}
HostConfig:{
AutoRemove:false
Binds:[]
BlkioWeight:0
Cgroup:""
CgroupParent:""
CgroupnsMode:"host"
ConsoleSize:[]
ContainerIDFile:""
CpuCount:0
CpuPercent:0
CpuPeriod:0
CpuQuota:0
CpuRealtimePeriod:0
CpuRealtimeRuntime:0
CpuShares:0
CpusetCpus:""
CpusetMems:""
DeviceCgroupRules:[]
Dns:[]
IOMaximumBandwidth:0
IOMaximumIOps:0
Init:false
IpcMode:"private"
Isolation:""
KernelMemory:0
KernelMemoryTCP:0
LogConfig:{
Config:{}
Type:"json-file"
}
MaskedPaths:[
"/proc/asound"
"/proc/acpi"
"/proc/kcore"
"/proc/keys"
"/proc/latency_stats"
"/proc/timer_list"
"/proc/timer_stats"
"/proc/sched_debug"
"/proc/scsi"
"/sys/firmware"
"/sys/devices/virtual/powercap"
]
Memory:0
MemoryReservation:0
MemorySwap:0
NanoCpus:0
NetworkMode:"bridge"
OomKillDisable:false
OomScoreAdj:0
PidMode:""
PortBindings:{
80/tcp:[
{
HostIp:"0.0.0.0"
HostPort:"19181"
}
]
}
Privileged:false
PublishAllPorts:false
ReadonlyPaths:[
"/proc/bus"
"/proc/fs"
"/proc/irq"
"/proc/sys"
"/proc/sysrq-trigger"
]
ReadonlyRootfs:false
RestartPolicy:{
MaximumRetryCount:0
Name:"unless-stopped"
}
Runtime:"runc"
ShmSize:67108864
UTSMode:""
Ulimits:[
{
Hard:65535
Name:"nofile"
Soft:65535
}
]
UsernsMode:""
VolumeDriver:""
VolumesFrom:[]
}
HostnamePath:"/share/CACHEDEV1_DATA/Container/container-station-data/lib/docker/containers/e1d073263db9e25de81316a1191d7c7a887a9f53a129b23ff5ea1a48bcb3d353/hostname"
HostsPath:"/share/CACHEDEV1_DATA/Container/container-station-data/lib/docker/containers/e1d073263db9e25de81316a1191d7c7a887a9f53a129b23ff5ea1a48bcb3d353/hosts"
Id:"e1d073263db9e25de81316a1191d7c7a887a9f53a129b23ff5ea1a48bcb3d353"
Image:"sha256:948cb53b1c4b2c0464035b21f4a6a432b4adca93d6171b52969e4e8f6030e20f"
LogPath:"/share/CACHEDEV1_DATA/Container/container-station-data/lib/docker/containers/e1d073263db9e25de81316a1191d7c7a887a9f53a129b23ff5ea1a48bcb3d353/e1d073263db9e25de81316a1191d7c7a887a9f53a129b23ff5ea1a48bcb3d353-json.log"
MountLabel:""
Mounts:[]
Name:"/jellyfin-vue"
NetworkSettings:{
Bridge:"lxcbr0"
EndpointID:""
Gateway:""
GlobalIPv6Address:""
GlobalIPv6PrefixLen:0
HairpinMode:false
IPAddress:""
IPPrefixLen:0
IPv6Gateway:""
LinkLocalIPv6Address:""
LinkLocalIPv6PrefixLen:0
MacAddress:""
Networks:{
bridge:{
EndpointID:""
Gateway:""
GlobalIPv6Address:""
GlobalIPv6PrefixLen:0
IPAddress:""
IPPrefixLen:0
IPv6Gateway:""
MacAddress:""
NetworkID:"10fced14cbb3f81a72c1f371e563e282050fbbcc099493c624af2588ef6c68c6"
}
}
Ports:{}
SandboxID:"a774e8680f1aebde20bb563cb438265d0e8ac64c9fb3a4f07b5121fbd324367d"
SandboxKey:"/var/run/docker/netns/a774e8680f1a"
}
Path:"/docker-entrypoint.sh"
Platform:"linux"
ProcessLabel:""
ResolvConfPath:"/share/CACHEDEV1_DATA/Container/container-station-data/lib/docker/containers/e1d073263db9e25de81316a1191d7c7a887a9f53a129b23ff5ea1a48bcb3d353/resolv.conf"
RestartCount:9
State:{
Dead:false
Error:""
ExitCode:1
FinishedAt:"2024-02-25T00:44:34.452987997Z"
OOMKilled:false
Paused:false
Pid:0
Restarting:true
Running:true
StartedAt:"2024-02-25T00:44:34.121479768Z"
Status:"restarting"
}
}

Screenshots

No response

Platform

Linux

Browser

Edge

Jellyfin server version

10.8.13

Additional context

No response

Sakujakira commented 8 months ago

Same behaviour on Synology DS920+, OS is DSM 7.2.1-69057 Update 3

ThibaultNocchi commented 8 months ago

I'm unfamiliar with QNAP or Synology, but it's normal in containers to keep the default ports used by the bundled apps, here Nginx. What you are supposed to do is bind these internal ports to another one on your host, such as 8080 for instance. There must be a way to do that, I can't see QNAP or Synology not allowing such a basic feature of Docker.

Sakujakira commented 8 months ago

You are of course right, there is a misunderstanding. This docker run -d -p 8080:80 ghcr.io/jellyfin/jellyfin-vue:unstable as well as a docker compose

jellyfin-vue:
    image: ghcr.io/jellyfin/jellyfin-vue:unstable
    container_name: jellyfin-vue
    hostname: jellyfin-vue
    environment:
      - DEFAULT_SERVERS=https://jellyfin.mydomain.tld
      - DISABLE_SERVER_SELECTION=1
    ports:
      - 8080:80
    networks:
      - web
    restart: unless-stopped

are leading to this error

vlastaw commented 8 months ago

I'm unfamiliar with QNAP or Synology, but it's normal in containers to keep the default ports used by the bundled apps, here Nginx. What you are supposed to do is bind these internal ports to another one on your host, such as 8080 for instance. There must be a way to do that, I can't see QNAP or Synology not allowing such a basic feature of Docker.

Oh, thank you. But you might have noticed, that I am already running Feishin in Docker on the same device, so I probably know, how to map ports in the virtual network. That's not the issue.

To my understanding the issue is the container itself, because its set to listen on port 80, but that (<1024) requires Privileged access in Linux. So no matter which port I map to it from the host, it won't work, because the guest machine always listens on 80. Feishin's Docker container (its web server) listens on port 9180, which is OK. Of course that's just my speculation, there might be a whole other issue. However it's not the host > guest port mapping. What happens, at least what the GUI displays, is that the guest machine is assigned a natted IP address (10.0.3.3) and loses it in a second (0.0.0.0).

ThibaultNocchi commented 8 months ago

There might be something up, with the internal 80 port not helping. Are you able to run the default Nginx image from DockerHub? It uses the 80 port to by default, maybe it'll help us point to something in our configuration.

PS: How was I able to see that you've mapped the port in your initial post? Sorry if I've missed it anyway.

vlastaw commented 8 months ago

Thank you for the tip. It seems I can.

image image

/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/ /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh 10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf 10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf /docker-entrypoint.sh: Sourcing /docker-entrypoint.d/15-local-resolvers.envsh /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh /docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh /docker-entrypoint.sh: Configuration complete; ready for start up 2024/03/04 17:38:29 [notice] 1#1: using the "epoll" event method 2024/03/04 17:38:29 [notice] 1#1: nginx/1.25.4 2024/03/04 17:38:29 [notice] 1#1: built by gcc 12.2.0 (Debian 12.2.0-14) 2024/03/04 17:38:29 [notice] 1#1: OS: Linux 4.2.8 2024/03/04 17:38:29 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 65535:65535 2024/03/04 17:38:29 [notice] 1#1: start worker processes 2024/03/04 17:38:29 [notice] 1#1: start worker process 30 2024/03/04 17:38:29 [notice] 1#1: start worker process 31 2024/03/04 17:38:29 [notice] 1#1: start worker process 32 2024/03/04 17:38:29 [notice] 1#1: start worker process 33 10.0.3.1 - - [04/Mar/2024:17:38:29 +0000] "\x16\x03\x01\x00\xEA\x01\x00\x00\xE6\x03\x03\x84%z\x19\xB8\x93\xF6\x928\xED|:\xD5\xFCW\xB8D\xACgfn\xEFP(\xDC0\xFB\xE0Z\x02\x12\x9C \x10\x88$\x83\xAF\xDA*\x9E\xAAjtG\x84\xD8G\x06\xBA\x90\xC38_\xD5\xEAj\x82\x8F\xB3%\xDAt\xDF\x00&\xC0+\xC0/\xC0,\xC00\xCC\xA9\xCC\xA8\xC0\x09\xC0\x13\xC0" 400 157 "-" "-" "-" 10.0.3.1 - - [04/Mar/2024:17:38:29 +0000] "GET / HTTP/1.1" 200 615 "-" "Go-http-client/1.1" "-"`

mhamzahkhan commented 8 months ago

Does it make sense to bind nginx to port 80? I mean if it is running in docker, you can just map it to a different port anyway when running the container.

ferferga commented 8 months ago

I initially thought the issue was that nginx coldn't bind to 80 inside the container, regardless of whatever port was used in the host. However, nginx's image is working fine and the issue it's not in this container?

mhamzahkhan commented 8 months ago

The image @vlastaw used is the stock nginx container, which starts up as root, so it can bind to 80. This is the official unprivileged image: https://hub.docker.com/r/nginxinc/nginx-unprivileged, which binds to 8080.

vlastaw commented 8 months ago

Well, then my theory of unpriviliged access to ports <1024 might be right, when even nginx is set not to use <1024 with unpriviliged users.

ferferga commented 8 months ago

@mhamzahkhan @vlastaw Can you guys try the builds from #2247

By accessing the checks' summary, you can download the exported docker image in the Artifacts section

mhamzahkhan commented 8 months ago

Yep looks like it works for me

vlastaw commented 8 months ago

I tried 2024-03-05.63981e1 and unfortunately it behaves the same. Loops these:

`2024/03/06 02:32:50 [emerg] 1#1: bind() to 0.0.0.0:80 failed (13: Permission denied) nginx: [emerg] bind() to 0.0.0.0:80 failed (13: Permission denied) ==== Starting Jellyfin Vue setup ====

Writing data to /usr/share/nginx/html/config.json... DEFAULT_SERVERS value: ALLOW_SERVER_SELECTION value: true ROUTER_MODE value: history

==== Setup finished! ====

2024/03/06 02:32:53 [emerg] 1#1: bind() to 0.0.0.0:80 failed (13: Permission denied) nginx: [emerg] bind() to 0.0.0.0:80 failed (13: Permission denied) ==== Starting Jellyfin Vue setup ====

Writing data to /usr/share/nginx/html/config.json... DEFAULT_SERVERS value: ALLOW_SERVER_SELECTION value: true ROUTER_MODE value: history

==== Setup finished! ====`

Sakujakira commented 8 months ago

This fix seems to work. I have Downloaded the Image from https://github.com/jellyfin/jellyfin-vue/actions/runs/8156492442/artifacts/1298415993 and loaded it:

docker load < docker_image.tar 
149c5e2d17c1: Loading layer [==================================================>]  5.698MB/5.698MB
6cbb65340ca2: Loading layer [==================================================>]  3.584kB/3.584kB
8ec2769da802: Loading layer [==================================================>]   5.12kB/5.12kB
55bd93303ff0: Loading layer [==================================================>]   38.4kB/38.4kB
d9e08de748ba: Loading layer [==================================================>]  2.358MB/2.358MB
Loaded image: jellyfin/jellyfin-vue:pr-build

I can start the image:

docker run -d -p 8080:80 c6062664f9d4

Logs are looking fine:

docker logs -f ac1b08b896083cf751c6b1fb64f888296ef697f73620b326aefc04a714e48a41
==== Starting Jellyfin Vue setup ====

Writing data to /usr/share/nginx/html/config.json...
DEFAULT_SERVERS value: 
ALLOW_SERVER_SELECTION value: true
ROUTER_MODE value: history

====      Setup finished!        ====

2024/03/06 08:27:21 [notice] 1#1: using the "epoll" event method
2024/03/06 08:27:21 [notice] 1#1: nginx/1.24.0
2024/03/06 08:27:21 [notice] 1#1: built by gcc 12.2.1 20220924 (Alpine 12.2.1_git20220924-r4) 
2024/03/06 08:27:21 [notice] 1#1: OS: Linux 4.4.302+
2024/03/06 08:27:21 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2024/03/06 08:27:21 [notice] 1#1: start worker processes
2024/03/06 08:27:21 [notice] 1#1: start worker process 9
2024/03/06 08:27:21 [notice] 1#1: start worker process 10
2024/03/06 08:27:21 [notice] 1#1: start worker process 11
2024/03/06 08:27:21 [notice] 1#1: start worker process 12
192.168.11.65 - - [06/Mar/2024:08:27:42 +0000] "GET / HTTP/1.1" 200 1949 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36" "-"
192.168.11.65 - - [06/Mar/2024:08:27:42 +0000] "GET /shared-CVtkxrq9.js HTTP/1.1" 200 1257 "http://192.168.21.153:8080/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36" "-"

And i am able to reach the webinterface

vlastaw commented 8 months ago

Unfortunately QTS won't install that file.

image

ThibaultNocchi commented 8 months ago

I guess the best way to test it yourself would be to build locally the image on your PC from the MR, then save it to tar from there and push it to your QNAP.

ferferga commented 8 months ago

@vlastaw My guess is that the image is amd64 only and your NAS might be arm64.

I'm merging the PR, it helped others, so probably you too (and that way you have a handy arm image to test). In case it still doesn't work after the merge, please let me know so I can reopen the issue.

neingeist commented 2 months ago

FWIW, this snippet in the docker-compose service allows not running the main nginx process as root:

    cap_add:                                                                                             
      - CAP_NET_BIND_SERVICE                                                                             
    user: 101:101  # nginx (inside the container)   
ferferga commented 2 months ago

@neingeist The docker images no longer run as root fyi.

neingeist commented 1 month ago

@neingeist The docker images no longer run as root fyi.

If you would start it .e.g. using docker run, it would start up as root, bind to :80 and then drops down to nginx. But last time I checked, you can't start it up as e.g. 101:101 (= nginx in the image), unless you add CAP_NET_BIND_SERVICE.

ferferga commented 1 month ago

@neingeist Which system?

neingeist commented 1 month ago

@neingeist Which system?

I checked again, on my Fedora 39 system there is no problem. Apparently Docker now lowers the privileged port range so you can run e.g. docker run -ti --rm=true -u 101:101 ghcr.io/jellyfin/jellyfin-vue:unstable.2024-09-20.28d1163 (running the image using uid 101 right away).

On my Synology NAS this did not work and I had to add CAP_NET_BIND_SERVICE if I wanted to run as uid 101 right away. (I am not investigating the reasons, as I have an update to the NAS pending anyway.)

ferferga commented 1 month ago

@neingeist I assumed you were using a really old version because I recall this not being the case after a long time. After further investigation, I discovered this has been the case since moby/moby#41030 (released with Docker 20.10) in 2020, almost 4 years ago and it's completely EOL and out of security updates since a year, so you should update your NAS ASAP 😅😅.

Besides being EOL, even if it still were supported as LTS (no LTS without that change is even suppirted though), I don't think there's a reason to stay in inferior versions in Docker really, so I don't think nothing should be changed in Jellyfin Vue's images.

neingeist commented 1 month ago

@ferferga Wasn't necessarily suggesting changing anything, just providing a workaround in case anyone tries the same as me :)

It's not only the Docker version, the kernel also needs to have /proc/sys/net/ipv4/ip_unprivileged_port_start for it to work.