dokku / dokku

A docker-powered PaaS that helps you build and manage the lifecycle of applications
https://dokku.com
MIT License
29.26k stars 1.92k forks source link

dokku does not respect EXPOSEd port #4067

Closed ltalirz closed 1 year ago

ltalirz commented 4 years ago

Description of problem

Pushing a git repository containing a Dockerfile that does EXPOSE 80. This is recognized by dokku as evidenced by the logs during the push:

-----> Setting config vars
       DOKKU_DOCKERFILE_PORTS:  80

Reading the dokku documentation, I expect that this will cause dokku to map the corresponding port on the remote.

However, when I then inspect the app on the dokku instance, the port mappings are not set up correctly - in particular, port 80 is still mapped to the default port 5000 inside the container:

dokku proxy:ports seekpath
-----> Port mappings for seekpath
-----> scheme             host port                 container port
http                      80                        5000
https                     443                       5000

How reproducible

I've tried

and keep getting the same result. The only solution I found is to manually set the corresponding port mapping.

Steps to Reproduce

  1. git clone https://github.com/materialscloud-org/tools-seekpath/
  2. git remote add dokku@yourdokku:appname
  3. ssh into dokku instance and run dokku proxy:ports appname

Actual Results

dokku proxy:ports seekpath
-----> Port mappings for seekpath
-----> scheme             host port                 container port
http                      80                        5000
https                     443                       5000

Expected Results

dokku proxy:ports seekpath
-----> Port mappings for seekpath
-----> scheme             host port                 container port
http                      80                        80
https                     443                       80

Following the dokku documentation, I may need another EXPOSE 443, which would then result in mapping port 443:443. In any case, the configuration above is the mapping that I would like to have.

Environment Information

dokku report APP_NAME output

ubuntu@dokku:~$ dokku report seekpath
-----> uname: Linux dokku 4.15.0-22-generic #24-Ubuntu SMP Wed May 16 12:15:17 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
-----> memory:
                     total        used        free      shared  buff/cache   available
       Mem:          32167         495       23005           1        8666       31235
       Swap:             0           0           0
-----> docker version:
       Client: Docker Engine - Community
        Version:           19.03.12
        API version:       1.40
        Go version:        go1.13.10
        Git commit:        48a66213fe
        Built:             Mon Jun 22 15:45:36 2020
        OS/Arch:           linux/amd64
        Experimental:      false

       Server: Docker Engine - Community
        Engine:
         Version:          19.03.12
         API version:      1.40 (minimum version 1.12)
         Go version:       go1.13.10
         Git commit:       48a66213fe
         Built:            Mon Jun 22 15:44:07 2020
         OS/Arch:          linux/amd64
         Experimental:     false
        containerd:
         Version:          1.2.13
         GitCommit:        7ad184331fa3e55e52b890ea95e65ba581ae3429
        runc:
         Version:          1.0.0-rc10
         GitCommit:        dc9208a3303feef5b3839f4323d9beb36df0a9dd
        docker-init:
         Version:          0.18.0
         GitCommit:        fec3683
-----> docker daemon info:
       Client:
        Debug Mode: true

       Server:
        Containers: 6
         Running: 3
         Paused: 0
         Stopped: 3
        Images: 30
        Server Version: 19.03.12
        Storage Driver: overlay2
         Backing Filesystem: extfs
         Supports d_type: true
         Native Overlay Diff: true
        Logging Driver: json-file
        Cgroup Driver: cgroupfs
        Plugins:
         Volume: local
         Network: bridge host ipvlan macvlan null overlay
         Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
        Swarm: inactive
        Runtimes: runc
        Default Runtime: runc
        Init Binary: docker-init
WARNING: No swap limit support
        containerd version: 7ad184331fa3e55e52b890ea95e65ba581ae3429
        runc version: dc9208a3303feef5b3839f4323d9beb36df0a9dd
        init version: fec3683
        Security Options:
         apparmor
         seccomp
          Profile: default
        Kernel Version: 4.15.0-22-generic
        Operating System: Ubuntu 18.04.4 LTS
        OSType: linux
        Architecture: x86_64
        CPUs: 8
        Total Memory: 31.41GiB
        Name: dokku
        ID: QLWH:QHXA:F2LD:BM2G:EGTH:EGE4:QQKL:PMG6:DQAD:BDDI:HITO:YH2W
        Docker Root Dir: /var/lib/docker
        Debug Mode: false
        Registry: https://index.docker.io/v1/
        Labels:
        Experimental: false
        Insecure Registries:
         127.0.0.0/8
        Live Restore Enabled: false

-----> sigil version: 0.6.0
-----> herokuish version:
       herokuish: 0.5.14
       buildpacks:
         heroku-buildpack-multi     v1.0.0
         heroku-buildpack-ruby      v215
         heroku-buildpack-nodejs    v171
         heroku-buildpack-clojure   v84
         heroku-buildpack-python    v170
         heroku-buildpack-java      v66
         heroku-buildpack-gradle    v31
         heroku-buildpack-scala     v87
         heroku-buildpack-play      v26
         heroku-buildpack-php       v176
         heroku-buildpack-go        v142
         buildpack-nginx            v13
-----> dokku version: dokku version 0.21.1
-----> dokku plugins:
       plugn: 0.5.0
         00_dokku-standard    0.21.1 enabled    dokku core standard plugin
         20_events            0.21.1 enabled    dokku core events logging plugin
         acl                  1.5.0 enabled    dokku plugin that can be used to restrict push privileges for app to certain users
         app-json             0.21.1 enabled    dokku core app-json plugin
         apps                 0.21.1 enabled    dokku core apps plugin
         builder-dockerfile   0.21.1 enabled    dokku core builder-dockerfile plugin
         builder-herokuish    0.21.1 enabled    dokku core builder-herokuish plugin
         buildpacks           0.21.1 enabled    dokku core buildpacks plugin
         certs                0.21.1 enabled    dokku core certificate management plugin
         checks               0.21.1 enabled    dokku core checks plugin
         clone                0.4.1 enabled    dokku git clone plugin
         common               0.21.1 enabled    dokku core common plugin
         config               0.21.1 enabled    dokku core config plugin
         docker-options       0.21.1 enabled    dokku core docker-options plugin
         domains              0.21.1 enabled    dokku core domains plugin
         enter                0.21.1 enabled    dokku core enter plugin
         git                  0.21.1 enabled    dokku core git plugin
         global-cert          0.4.5 enabled    manages a global certificate for dokku
         logs                 0.21.1 enabled    dokku core logs plugin
         network              0.21.1 enabled    dokku core network plugin
         nginx-vhosts         0.21.1 enabled    dokku core nginx-vhosts plugin
         plugin               0.21.1 enabled    dokku core plugin plugin
         postgres             1.11.5 enabled    dokku postgres service plugin
         proxy                0.21.1 enabled    dokku core proxy plugin
         ps                   0.21.1 enabled    dokku core ps plugin
         repo                 0.21.1 enabled    dokku core repo plugin
         resource             0.21.1 enabled    dokku core resource plugin
         scheduler-docker-local 0.21.1 enabled    dokku core scheduler-docker-local plugin
         shell                0.21.1 enabled    dokku core shell plugin
         ssh-keys             0.21.1 enabled    dokku core ssh-keys plugin
         storage              0.21.1 enabled    dokku core storage plugin
         tags                 0.21.1 enabled    dokku core tags plugin
         tar                  0.21.1 enabled    dokku core tar plugin
         trace                0.21.1 enabled    dokku core trace plugin
=====> seekpath app information
       App deploy source:
       App dir:                       /home/dokku/seekpath
       App locked:                    false
=====> seekpath buildpacks information
       Buildpacks list:
CN = *.materialscloud.io
error 20 at 0 depth lookup: unable to get local issuer certificate
=====> seekpath ssl information
       Ssl dir:                       /home/dokku/seekpath/tls
       Ssl enabled:                   true
       Ssl hostnames:                 *.materialscloud.io
       Ssl expires at:                Oct 13 08:12:54 2020 GMT
       Ssl issuer:                    C = US, O = Lets Encrypt, CN = Lets Encrypt Authority X3
       Ssl starts at:                 Jul 15 08:12:54 2020 GMT
       Ssl subject:                   subject=CN = *.materialscloud.io
       Ssl verified:                  self signed
=====> seekpath checks information
       Checks disabled list:          none
       Checks skipped list:           none
=====> seekpath docker options information
       Docker options build:
       Docker options deploy:         --restart=on-failure:10
       Docker options run:
=====> seekpath domains information
       Domains app enabled:           true
       Domains app vhosts:            seekpath.materialscloud.io
       Domains global enabled:        true
       Domains global vhosts:         materialscloud.io
=====> seekpath git information
       Git deploy branch:             master
       Git global deploy branch:      master
       Git keep git dir:              false
       Git rev env var:               GIT_REV
       Git sha:                       037e6b4
       Git last updated at:
=====> seekpath network information
       Network attach post create:
       Network attach post deploy:
       Network bind all interfaces:   false
       Network web listeners:         172.17.0.2:80
=====> seekpath nginx information
       Nginx access log path:         /var/log/nginx/seekpath-access.log
       Nginx bind address ipv4:
       Nginx bind address ipv6:       ::
       Nginx error log path:          /var/log/nginx/seekpath-error.log
       Nginx hsts:                    true
       Nginx hsts include subdomains: true
       Nginx hsts max age:            15724800
       Nginx hsts preload:            false
       Nginx proxy read timeout:      60s
       Nginx last visited at:         1594890503
=====> seekpath proxy information
       Proxy enabled:                 true
       Proxy port map:                http:80:5000 https:443:5000
       Proxy type:                    nginx
=====> seekpath ps information
       Processes:                     1
       Deployed:                      true
       Running:                       true
       Restore:                       true
       Restart policy:                on-failure:10
       Ps can scale:                  true
       Status web.1:                  running    (CID: c028cffee760)
=====> seekpath scheduler-docker-local information
       Scheduler docker local disable chown:
=====> seekpath storage information
       Storage build mounts:
       Storage deploy mounts:
       Storage run mounts:

How (deb/make/rpm) and where (AWS, VirtualBox, physical, etc.) was Dokku installed?:

Installed via ansible-dokku role using latest versions , see PR https://github.com/dokku/ansible-dokku/pull/76

josegonzalez commented 4 years ago

Haven't read through this, but my thought process here is that we should simplify port handling such that we drop anything that isn't managed by the proxy plugin.

ltalirz commented 4 years ago

Haven't read through this, but my thought process here is that we should simplify port handling such that we drop anything that isn't managed by the proxy plugin.

As someone who isn't familiar with how dokku works internally, it would be helpful if you could elaborate a bit on this. I.e. which currently active mechanisms of port handling do you suggest to drop/replace? Where should one look in order to fix this?

About this issue itself: it would be great if others could comment whether they observe the same behavior? Since this potentially affects anybody using Dockerfiles for deployment I would be surprised if I was the only one experiencing this (or something about our setup must be very special).

josegonzalez commented 4 years ago

DOKKU_DOCKERFILE_PORTS

Remove stuff like that (and PROXY_SSL_PORT) in favor of just whatever is settable by the proxy plugin. Proxying and domain handling is quite "rough" in Dokku atm (lots of hooks to jack into/support), which makes debugging/feature development difficult.

ltalirz commented 4 years ago

If I understand correctly, this would mean an extra step users need to keep in mind when deploying any app that uses Dockerfiles and doesn't serve on the default port 5000. I.e. something like

ssh -t dokku@materialscloud.io proxy:ports-set seekpath https:443:80

I wonder how many people will forget this and complain that the app isn't working... perhaps one could show a hint about this on the error page that dokku displays when the container isn't reachable?

On the other hand, the behavior of EXPOSE XX can be non-obvious as well, since it can be inherited from parent images (that's from the docker perspective, not sure whether dokku's handling of the directive works that way as well). I.e. in principle the base image might decide to no longer EXPOSE a port and break your app (or it may EXPOSE ports you don't want to expose, and there is currently no way to "unexpose" in docker).

ltalirz commented 4 years ago

P.S. If the automatic configuration of the port mapping is dropped, it would become very useful to be able to at least configure the default target for the port mapping - in case your dokku instance typically receives apps that are listening on a port other than 5000.

josegonzalez commented 4 years ago

What I'd like to do is separate the automatically detected ports from the ones manually set. So if you don't specify overrides, we'll detect on deploy and set those as some "autodetected" versions somewhere. We'll use the current detected proxy ports as is, but otherwise new apps will be autodetected, and when someone force sets ports, we'll let them know that they will need to force override them later (so no weirdness around your values being overriden).

ltalirz commented 4 years ago

I'm not sure I follow 100% but keeping a record of autodetected ports separately and warning about manual overrides sounds like a great approach. Happy to help test / review any PRs in this direction.

AubreyHewes commented 4 years ago

About this issue itself: it would be great if others could comment whether they observe the same behavior?

@ltalirz Yeah we have this all the time with Dockerfile deployments ;-) Known/current solution is to after initial deploy manually fix the proxy ports. We have always sort of accepted this (sic).

I would definitely like that a Dockerfile deployment actually gets a correct proxy configuration.. removing the extra manual proxy cmd requirement.

Another same kind of issue is when an app changes its internal ports.. i.e. was first an app that listened internally on 5000 (expressjs?).. then the app was updated and listens internally on 3000 (nextjs?)... in this case we (DevOps) know we need to change the proxy ports.. though for this case it may be nicer to add extra port info to the, sort of unused, app.json or such like? Though I am diluting the issue with this.

AubreyHewes commented 4 years ago

@ltalirz I also think that when using Dockerfile deployment it is up to the DevOp to decide if the app should be exposed via the proxy... Using a Dockerfile deployment entails extra config. Some Dockerfile deployments may not want to be exposed via http... just linked to other apps... ? Think elasticsearch or suchlike..

Though I will give a simple idea a go for honouring the get_dockerfile_exposed_ports (which is the first found exposed port according to the code)...

sort of a kind of an idea:

  # extract first port from Dockerfile
  local DOCKERFILE_PORTS=$(get_dockerfile_exposed_ports Dockerfile)
  if [[ -n "$DOCKERFILE_PORTS" ]]; then
    config_set --no-restart "$APP" DOKKU_DOCKERFILE_PORTS="$DOCKERFILE_PORTS"
    # Check: app is exposed?/app has http?/app has proxy?
    # Check: detected port is something we want to expose via the proxy? 80/8000/8080/3000(nextjs lots of js stuff)/5000(expressjs?) ?
    # WARN this may break stuff... who knows who or what is the first port?
    config_set --no-restart "$APP" DOKKU_PROXY_PORT_MAP="http:80:$DOCKERFILE_PORTS"
    # TODO has TLS then also add proxy https mapping.. `https:443:x`
    #config_set --no-restart "$APP" DOKKU_PROXY_PORT_MAP="http:80:$DOCKERFILE_PORTS https:443:$DOCKERFILE_PORTS"
  fi

Possibly with a warning if it is a non, known (sic), "http" port.. so just do it for 80/8000/8080/3000(nextjs lots of js stuff)/5000(expressjs?) ? and ignore others? Again not all Dockerfile deployments are exposed http apps...

Better would be just doing this for port 80 (most probable a website) and otherwise document to utilize something like app.json/dokku/port-mapping when using the Dockerfile deployment feature?

ltalirz commented 4 years ago

Hi @AubreyHewes, @josegonzalez mentioned above that he would prefer removing DOKKU_DOCKERFILE_PORTS entirely - perhaps he would be willing to give a few more concrete pointers on which parts of the code should be changed, and in what way? (+ perhaps some hints on new variable naming, if we need to introduce any)

TimHal commented 2 years ago

Related observation: When deploying an app via docker image and attaching it to a dokku network the original port for the apps Network web listeners does not get updated when the app's exposed port changes in the docker image. Using dokku network:report will always show the original port in the line for Network web listeners, even though the new port is being used as expected.

alexgleason commented 2 years ago

I agree Dokku's proxy doesn't work with EXPOSE, but the actually bad thing is that EXPOSE does change the behavior. So if you have a Dockerfile with EXPOSE (which, you can't remove if you inherited it from a parent image), you get stuck in a halfway state after deploying image, and you can't solve it without manually changing the proxy config.

To work around it, I ended up writing my nginx Dockerfile like this:

FROM nginx:stable-alpine
EXPOSE 5000
ENV PORT=5000
COPY installation/docker.conf.template /etc/nginx/templates/default.conf.template
COPY --from=build /app/static /usr/share/nginx/html

Then I made my docker.conf.template respect the PORT variable:

server {
  listen ${PORT};
  # ...

So now even though Dokku wants to proxy 5000 despite the EXPOSE 80, by just exposing 5000 it works correctly now on the first deployment without any additional configuration.

If you want to see it in context, the full MR is here: https://gitlab.com/soapbox-pub/soapbox-fe/-/merge_requests/1468

aradalvand commented 1 year ago

As a Dokku newbie this caused a lot of confusion for me, I was getting a "We're sorry, but something went wrong." error and couldn't figure out what was wrong with my setup, until I eventually stumbled into this issue.

Until this bug is fixed, would you consider mentioning it in the documentation in the meantime? Currently, the documentation is not consistent with the actual behavior of Dokku.

josegonzalez commented 1 year ago

Closing as there is a pull request open.

In the pull request:

If I get time, I'll also add the following: