Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Additional Docker Features #1255

Merged
merged 28 commits into from
Aug 3, 2023
Merged

Additional Docker Features #1255

merged 28 commits into from
Aug 3, 2023

Conversation

natankeddem
Copy link
Contributor

The current release dockerfile is limited in features. I propose adding the following 2 additional features:

  • UID and GID configurable via environmental variables. This will mitigate permissions issues when volume mounting to the host. For example volume mounting the .nicegui directory for persistent storage of data on container destruction.
  • Using an entrypoint will allow signal passthrough to the app. The SIGTERM and SIGKILL signals are triggered during various docker housekeeping tasks. It is useful for the app to be able to intercept these signals and act accordingly.

@falkoschindler falkoschindler added this to the 1.3.7 milestone Jul 27, 2023
@falkoschindler falkoschindler added the enhancement New feature or request label Jul 27, 2023
Copy link
Member

@rodja rodja left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work @natankeddem. Thanks. Could you move the docker-compose.yml into a separate example? And maybe also providing a demo main.py and mounting the local folder as /app? That way users could see right away copy the folder and start hacking.

release.dockerfile Show resolved Hide resolved
release/docker-entrypoint.sh Outdated Show resolved Hide resolved
release/docker-compose.yml Outdated Show resolved Hide resolved
@natankeddem natankeddem requested a review from rodja July 28, 2023 00:47
@rodja
Copy link
Member

rodja commented Jul 28, 2023

I've made some changes to the PR, @natankeddem. I hope you approve:

  • using storage.user and a note taking demo for the example instead of storage.global to demonstrate the passing of a storage secret from docker-compose to app
  • restructured the README.md to list the individual features and benefits
  • only mounting the app dir (and not an additional .nicegui folder) because this already makes the .nicegui folder within /app persistent
  • setting PYTHONUNBUFFERED env to true, so print statements are directly shown in the logs and not buffered
  • reintroduced CMD so users of the image can change the startup script name more easily

@rodja
Copy link
Member

rodja commented Jul 28, 2023

One other thing: could we get rid of the entrypoint by moving it all into the release.dockerfile?

FROM python:3.11.3-slim
ARG VERSION

LABEL maintainer="Zauberzeug GmbH <info@zauberzeug.com>"

RUN python -m pip install nicegui==$VERSION itsdangerous isort docutils requests

WORKDIR /app

COPY main.py README.md prometheus.py ./
COPY examples ./examples
COPY website ./website
RUN mkdir /resources
COPY docker-entrypoint.sh /resources
RUN chmod 777 /resources/docker-entrypoint.sh

ENV PUID=1000 PGID=1000
ENV PYTHONUNBUFFERED True

RUN groupadd -g "$PGID" appgroup && \
    useradd --create-home --shell /bin/bash --uid "$PUID" --gid "$PGID" appuser && \
    mkdir -p /app /usr/share/fonts /var/cache/fontconfig /usr/local/share/fonts && \
    chown -R appuser:appgroup /app && \
    chmod -R 755 /usr/share/fonts /var/cache/fontconfig /usr/local/share/fonts

COPY --chown=appuser:appgroup . /app

WORKDIR /app

USER appuser

CMD ["python", "main.py"]

@natankeddem
Copy link
Contributor Author

So a few things to unpack here. The reasons for using entrypoint script over cmd:

  • Signals from my understanding and playing around with things in the past; do not get passed when using CMD. Are you finding they are working with the CMD as well? I can test a bit later on but I have done previous testing that showed this difference.
  • If you do not execute the uid/gid code inside of a script with CMD or entrypoint it is not as versatile. Remember putting the code directly in the dockerfile means that these values will only get used on the build phase of the docker image. Typically people pull an image and then execute the compose up/down on that same image. If you change the uid/gid after the build step in the compose it will not change any permissions on the container because those RUN cmds will never be executed after the build. I hope I am expressing this in a way that makes sense it is a bit of a hard subject to wrap your head around. Basically there is a build stage lifetime and runtime stage lifetime for env variables.
  • We could make a entrypoint script that would pass arguments from the dockerfile down. IE
    ENTRYPOINT ["/resources/docker-entrypoint.sh", "python3", "main.py"]

@natankeddem
Copy link
Contributor Author

natankeddem commented Jul 28, 2023

Some explanation on docker signal handling and why endpoint is needed.
Reference

On a side note once signaling is enabled the container will not only shutdown gracefully but will also shutdown noticeably quicker.

@natankeddem
Copy link
Contributor Author

I think my original intent with the compose was to show more of an end user/end consumer usage. These end users would be people that had no need to manipulate the application code, they just want to utilize the functionality of the container. In this usage you would want to have persistent storage of just the .nicegui directory and would not share in your own code via a /app mount. Maybe it makes sense to remove the /app volume mount and create a 2nd compose or alter the development dockerfile in a separate PR (or in this one) to add these features since the development compose already mounts /app?

@natankeddem
Copy link
Contributor Author

After some soul searching here I want to quantify what we are trying to do here. We can categorize the NiceGUI audience in 3 different classes of users.

  1. NiceGUI Developers - Users who want to contribute and modify NiceGUI itself.
  2. App Developers - Users who want to build applications using NiceGUI utilizing the established NiceGUI feature set without modification.
  3. App Customers - Users who are utilizing and deploying applications developed using NiceGUI with no intention of modifying code.

Perhaps all 3 classes of users deserve their own docker-compose examples/implementations? Sorry if I am complicating things I just want to clarify the customers for what we are developing here and what their specific needs might be. Hopefully this will get us something that is more useful and organized at the end of the process.

to demonstrate the signal passing problem
@rodja
Copy link
Member

rodja commented Jul 29, 2023

Signals from my understanding and playing around with things in the past; do not get passed when using CMD. Are you finding they are working with the CMD as well?

Yes, it works for me:

➜  docker compose up          
[+] Running 1/1
 ⠿ Container docker_features-nicegui-1  Recreated                                                                                               0.1s
Attaching to docker_features-nicegui-1
docker_features-nicegui-1  | NiceGUI ready to go on http://localhost:8080, and http://192.168.16.2:8080
^CGracefully stopping... (press Ctrl+C again to force)
[+] Running 1/1
 ⠿ Container docker_features-nicegui-1  Stopped                                                                                                 0.5s
canceled
➜  docker compose logs                   
docker_features-nicegui-1  | NiceGUI ready to go on http://localhost:8080, and http://192.168.16.2:8080
docker_features-nicegui-1  | Shutdown has been initiated!

This is in line to the reference you posted above.

If you change the uid/gid after the build step in the compose it will not change any permissions on the container because those RUN cmds will never be executed after the build.

True! The main downside of using the entrypoint besides more code is that the permissions of mounted app or .nicegui directory will change to whatever is configured as uid/gid. Because we use 1000 as default, it will alter your source code if it is used without providing the right parameters. In my opinion it would be ok if the uid/gid is only configurable through https://docs.docker.com/engine/security/userns-remap/ or by building a custom image which only uses the zauberzeug/nicegui image as base.

Your categorization of the three audiences is very helpful. Thanks.

  1. NiceGUI Developers are currently covered by the development.dockerfile in combination with the docker-compose.yml in the root directory. There is a draft pull request Implement devcontainer for NiceGUI dev environment #1057 to enhance that with a devcontainer.
  2. App Developers should be provided with a good example on how they could use the official zauberzeug/nicegui image from Docker Hub to develop their app.
  3. App Customers (utilize) and App DevOps (deploy) the code from the App Developers. Hence, it is the responsibility of these developers to provide the instructions. We could help them by giving pointers and suggestions. Currently we do this (not so well) in https://nicegui.io/documentation#deployment.

This PR should focus on 2 with some hints for 3 in my opinion.

@natankeddem
Copy link
Contributor Author

It is interesting that signal passing is working with CMD. At one point I thought I tested it and it didn't work and I saw the evidence for why in my research; reference previous link. Although, now when I test it again it seems to work. Perhaps I was mixing things up in my iterations of testing.

I still think UID/GID mimicking is a good way to go. The user namespace remapping doesn't seem to be used by many and I myself will admit that I have a hard time wrapping my head around it. It seems rather complicated and requires the user have fairly advanced docker knowledge, while the ability to match your host user uid/gid is fairly simple as long as you have fairly basic linux knowledge. The PUID/PGID environmental variables have become popular since linuxserver.io seems to use that in all of there images so many people are already familiar with their usage. We could even probably use the same docker-entrypoint.sh for all of the dockerfiles by making it more generic as you have shown.
dockerfile:
ENTRYPOINT ["/resources/docker-entrypoint.sh", "cd /app && python main.py"]
or
ENTRYPOINT ["/resources/docker-entrypoint.sh", "cd /app && python3 -m debugpy --listen 5678 main.py]
docker-entrypoint.sh
exec su appuser -p -c "$@"

@natankeddem
Copy link
Contributor Author

I played around with different combinations a bit more and read up:
https://docs.docker.com/engine/reference/builder/#understand-how-cmd-and-entrypoint-interact

So there appears to be major differences in how docker handles standard string CMD/ENTRYPOINT vs JSON-array notation. Therefore the current implementation:
CMD python3 main.py
does not pass signals, but:
CMD ["python3", "main.py"]
Will pass the signals.

Now is the question of where do you want to go with the try entrypoint? Currently the .nicegui directory/contents are created with the following permissions:
drwxr-xr-x 2 root root 4096 Jul 31 10:08 .nicegui
I don't think this is ideal and think being able to set the UID/GID would be useful. I have found my users have issues in certain OSes such as Unraid without this feature because of how it automatically creates volume mount locations for users. Without this feature you either need to chmod this directory or you need elevate privileges to modify/delete these files.

Where do you want to go from here?

@rodja
Copy link
Member

rodja commented Jul 31, 2023

Thanks for digging so deep and being persistent, @natankeddem. I really like to get convinced :-)

The PUID/PGID environmental variables have become popular since linuxserver.io seems to use that in all of there images

The argument "behind" that sentence is really convincing: it's much simpler for users to set env variables than using remapping or creating custom Dockerfiles. Another benefit of using ENTRYPOINT to set the user would be that the images which are build upon the zauberzeug/nicegui image do not need to switch back to root to install software.

So let's go with the ENTRYPOINT version.

@natankeddem
Copy link
Contributor Author

I think the readability goes up with utilizing the ENTRYPOINT/CMD combo so I went that way. With this change to a generic entrypoint it should be useable with the development dockerfile in the future if needed.

@rodja
Copy link
Member

rodja commented Aug 1, 2023

I think the readability goes up with utilizing the ENTRYPOINT/CMD combo so I went that way.

Very cool. Unfortunately I had some issues with signal passing which I could only repair by using gosu instead of su. I also made some other changes like improving README.md and cleanups. Please have a look. I think we are ready for merge. Do you agree?

@rodja
Copy link
Member

rodja commented Aug 2, 2023

I've used the following script to see how fast the shutdown is happening:

#!/usr/bin/env bash

docker build -t nicegui:latest --build-arg VERSION=1.3.6 -f release.dockerfile .
docker run -d --name nicegui_container nicegui:latest
sleep 2 # give container some time to start properly
start_time=$(date +%s)
docker stop nicegui_container
end_time=$(date +%s)
elapsed_time=$((end_time - start_time))
echo "Shutdown time: $elapsed_time" seconds
echo "Logs:"
docker logs nicegui_container
echo "----"
docker container rm nicegui_container && echo 'removed container'

Time to shutdown was 0 seconds with gosu, but 3 seconds with su. In my opinion it makes sense to use gosu because it elevates the CMD to become PID 1 which will then receive the signals from Docker.

@rodja
Copy link
Member

rodja commented Aug 3, 2023

I've switched from gosu to setpriv like PhantomBot/PhantomBot#3068 and suggested by tianon/gosu#59. It gets rid of the extra package and still steps down from root while becoming the PID 1 (which has may benefits -- even if I could not find a super trustworthy source which summarized it well).

@rodja rodja merged commit 03fe760 into zauberzeug:main Aug 3, 2023
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants