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

Use Node Alpine Version as Docker image base & use Yarn instead of NPM #8704

Closed

Conversation

stevefan1999-personal
Copy link
Contributor

@stevefan1999-personal stevefan1999-personal commented Oct 1, 2020

Well the prebuilt binaries for benchpress-rust is built against GLIBC, which is a big no no for Alpine.

I tried to use Alpine and thus that prebuilt binary cannot import the symbols for GLIBC, neither libc6-compat do. I have succeeded to install rust from Alpine Edge and let Neon do its job, but it will increase the build time but roughly 1.5x.

It is fixed a year later

The image size will drop more than 100MB when compared normally.

@CLAassistant
Copy link

CLAassistant commented Oct 1, 2020

CLA assistant check
All committers have signed the CLA.

@stevefan1999-personal stevefan1999-personal changed the title Use Alpine Linux as Docker image base Use Debian Buster slim version as Docker image base & use Yarn instead of NPM Oct 1, 2020
@julianlam
Copy link
Member

Hi @stevefan1999-personal -- is there a specific reason you've updated the Dockerfile to use these configurations?

@stevefan1999-personal
Copy link
Contributor Author

stevefan1999-personal commented Oct 1, 2020

@julianlam I want to image size to be as small as possible to better fit in a k8s cluster. Almost all of us use Alpine as base image.

@julianlam
Copy link
Member

Right, and the choice between Debian Buster and Alpine...?

You should not bundle imagemagick in the file, as it is not required by NodeBB unless you are using the imagemagick plugin, and if so you'll want to use a forked Dockerfile.

@stevefan1999-personal
Copy link
Contributor Author

stevefan1999-personal commented Oct 1, 2020

@julianlam And I think that hacking the Dockerfile to use Alpine will be very dangerous and it will affect downstream images as well, so I rather take a step back and look for safer alternatives. And immediately I see that a slim version of node is available so I changed to that, plus I deleted all that apt cache so that it should have a comparable size to what you would get with Alpine.

Also in the official guide to install NodeBB in Debian, ImageMagick is required. And I see that makes sense for others to derive a new image that install ImageMagick on their own. Now I removed it so the size should be the bare minimum now.

I have pushed my modifications to Docker Hub. You can compare the size difference here:
https://hub.docker.com/r/stevefan1999/nodebb_node/tags
https://hub.docker.com/r/nodebb/docker/tags

@julianlam
Copy link
Member

Thanks for the explanation. The change to use yarn is certainly ok, although I think some flag needs to be set in NodeBB setup, I will review again later today.

@julianlam
Copy link
Member

@nilsramsperger this is an unsolicited plea for a second pair of eyes on this PR 😁

@stevefan1999-personal
Copy link
Contributor Author

I think it LGTM. The image size now is trimmed down by 2,27x, a very considerable shrink in sizes.

@nilsramsperger
Copy link
Contributor

Switching to a smaller base image to shrink image size is a good idea. Buster-slim is a step in the right direction.
If I understand the conversation right. The use of alpine is problematic with the initial image magic integration. In general, alpine is usable. I use it in my NodeBB image with no problems.
I'm not familiar with the advantages of yarn compared to npm. A quick look a yarn's documentation just points out a better speed. So for precompiled Docker images from Dockerhub it bears no real benefit for the downloading user.

@julianlam
Copy link
Member

julianlam commented Oct 1, 2020

@stevefan1999-personal Let's keep it simple and revert back to npm. I am okay if you want to switch to Alpine Linux since @nilsramsperger is using it. If you need imagemagick support, then ok to stick with Debian Buster as well.

For reference here is his Dockerfile

@pitaj
Copy link
Contributor

pitaj commented Oct 1, 2020

If somebody wants to open an issue on benchpress I can switch it to use musl-libc instead which should make it more portable across Linux distros.

@stevefan1999-personal
Copy link
Contributor Author

stevefan1999-personal commented Oct 1, 2020

@nilsramsperger fine, i'll revert to npmI found out that one key point of having a dramatic size decrease is due yarn and how it deletes cache. I need to have some reconsideration now...🤔 from npm with 656.39 to yarn with 540.54 in image MB size...
@julianlam well in alpine you can also just apk --no-cache add imagemagick
@pitaj rather I think you should provide extra builds with musl and not replace them. right now i can manually compile benchpress-rust by using the edge repo and then download the prerequisites for neon

I will squash the commits for the one final round

@julianlam
Copy link
Member

Thanks @stevefan1999-personal 😄

@nilsramsperger
Copy link
Contributor

@stevefan1999-personal good to know that yarn is somewhat more efficient on cache deletion than npm.
As in most cases, there are multiple ways to reach a goal. While optimizing my NodeBB image for size, I came to Docker multistage builds. That way you only take the files you really need for the image and dump everything else. In the end, it's an efficient way of cache deletion as well.

@stevefan1999-personal stevefan1999-personal changed the title Use Debian Buster slim version as Docker image base & use Yarn instead of NPM Use Node Alpine Version as Docker image base & use Yarn instead of NPM Aug 19, 2021
@NazimHAli
Copy link
Contributor

Any news on this? Need help with any outstanding issues? After switching to yarn, I just can't use npm anymore unless I have to :p

@stevefan1999-personal
Copy link
Contributor Author

@NazimHAli yes, there is no way to keep node_modules for some reason, especially when it involved plugins, and I realized Docker might not be the right tech for this.

@stevefan1999-personal
Copy link
Contributor Author

I planned to use it in Kubernetes, but then I realized I cannot do layered volumes on demand, so I wanted to have a separate plugin volume that layered on top of the base image, so I don't have to worry about losing plugin after restarting.

Someone made it with Synching and decided to recursively link everything into it, and I think this is not elegant.

It is also possible if I use FUSE based OverlayFS implementation (because it requires root), but the performance won't be pretty.

@Brutus5000
Copy link
Contributor

Brutus5000 commented Dec 16, 2021

@stevefan1999-personal I made it working on kubernetes, but it is ugly so far.
(It could be improved a little bit by replacing sed with envsubst.)

My PR #10036 would solve the module problem.
Also PR #9850 would solve the splitting of secret and config problem.

But until then I hacked it with an initContainer in the deployment and a config.json template and a buildscript mounted from the configMap.
Also I added a one-time installer script

@stevefan1999-personal
Copy link
Contributor Author

By merging the user layer with the base layer using fuse-overlayfs, you can successfully save the user-initiated changes such as plugin and theme install. The only problem: it requires FUSE and SYS_ADMIN, which is considered unsafe to some people with a concern of breaching container security. But so far it works for me.

@julianlam
Copy link
Member

@nilsramsperger Can you kindly do a test run and let us know if there are any unexpected behaviours with this new image?

@stevefan1999-personal
Copy link
Contributor Author

@nilsramsperger Can you kindly do a test run and let us know if there are any unexpected behaviours with this new image?

Oh also expect longer build times for workflow since qemu emulation is prohibitively slow. At the very best we could just include arm64 and amd64 to lose 32-bit Raspberry Pi support (armv7). That might pity some people but any hobbyst should be able to figure out how to build an image matching their platform. I needed arm64 since I run it on Ampere.

@oplik0
Copy link
Contributor

oplik0 commented Jul 10, 2022

I'd say at the very least ppc64le (I wonder how many PowerPC devices run NodeBB...) and arm v6 should go. I'd even be in favor of dropping v7.
The last fairly popular device pre-arm v7 was the original RPi Zero, but even the Zero 2W is ARMv8 now. Mainline RPis are on v8 since RPi3 and there is now official 64 bit Raspberry Pi OS.

Also, not sure if MongoDB docker image works on 32 bit ARM anyway.

EDIT: Nope, it doesn't - amd64 and arm64 only. Redis and Postgres do, but I think it might be fine to just follow the default DB choice for NodeBB. The question is how much time it adds though, if it's not a lot I don't think keeping v7 would be bad.

Signed-off-by: steve <29133953+stevefan1999-personal@users.noreply.github.com>
@stevefan1999-personal
Copy link
Contributor Author

I'd say at the very least ppc64le (I wonder how many PowerPC devices run NodeBB...) and arm v6 should go. I'd even be in favor of dropping v7. The last fairly popular device pre-arm v7 was the original RPi Zero, but even the Zero 2W is ARMv8 now. Mainline RPis are on v8 since RPi3 and there is now official 64 bit Raspberry Pi OS.

Also, not sure if MongoDB docker image works on 32 bit ARM anyway.

EDIT: Nope, it doesn't - amd64 and arm64 only. Redis and Postgres do, but I think it might be fine to just follow the default DB choice for NodeBB. The question is how much time it adds though, if it's not a lot I don't think keeping v7 would be bad.

Okay. That should do it

@nilsramsperger
Copy link
Contributor

@julianlam I just did a small test run on the code, using the redis profile. The forum can be set up and be started. No problems this far.

As mentioned in previous posts, the startup takes quite some time. This leads to some bad user experience, since the software feels like it hung up several times:

  • The web installer can't be accessed right after starting the containers.
  • The installation itself, after providing user and db parameters, takes very long. I had to F5, to get to the "Start NodeBB" screen.

The performance definitely needs some improvement (IMHO).

Greets
Nils

Signed-off-by: steve <29133953+stevefan1999-personal@users.noreply.github.com>
else
echo "Config file not found at $CONFIG"
echo "Starting installer"
./nodebb install --config=$CONFIG
Copy link
Contributor

Choose a reason for hiding this comment

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

I just noticed that this is running install instead of setup like the current dockerfile. This would be a breaking change AFAIK, since unlike setup this requires user interaction to actually start NodeBB.

However, current Dockerfile requires a SETUP env variable to run setup, as such this could probably be left as-is and instead before the config file is checked the dockerfile could check for that variable and run ./nodebb setup if it is set.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

IIRC setup runs an interactive CLI that requires user input but on a containerized environment this is close to impossible (and there is a lot of gimmicks using docker attach). Since I use Kubernetes to host NodeBB, that's even more impossible, and that's why I need to resort to using web installer instead. But I do agree this is a breaking change. Maybe we could add a switch for this.

Copy link
Contributor

@oplik0 oplik0 Jul 13, 2022

Choose a reason for hiding this comment

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

Setup can run with just env variables, which is why the current dockerfile has it (and probably why it requires a separate variable to run it).

As I said, I think a preferred solution might be leaving install as is and instead checking for the SETUP env variable before the two existing paths for running NodeBB. It'd maintain compatibility with current Dockerfile while adding the installation functionality if config doesn't exist and the user didn't prepare for unattended setup.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh I have implemented the "init" verb now, but should I keep it as setup or install, though?

Copy link
Contributor

@oplik0 oplik0 Jul 13, 2022

Choose a reason for hiding this comment

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

As I said: I think install by default is a good choice, but for the sake of backwards compatibility the script should run setup if an environmental variable called SETUP is set (see this part of the current dockerfile: test -n "${SETUP}" && ./nodebb setup).
So essentially, I think your solution is better for most cases (and being able to specify command is even better), but I think the previous one should still work not to break someone's workflow without a decent reason.

Can't suggest code changes in this thread since it's outdated, but something like

if [ -n $SETUP ]; then
  ./nodebb setup --config $CONFIG
elif [ -f $CONFIG ]; then
(rest of current code)

Would ensure current setups work.

This would allow you to change between "setup" (automated setup using environmental variables which is the current preferred way to run containerized NodeBB) or "install" (web install that guides user to fill in connection information, which is similar to WordPress)

Signed-off-by: steve <29133953+stevefan1999-personal@users.noreply.github.com>
Signed-off-by: steve <29133953+stevefan1999-personal@users.noreply.github.com>
Signed-off-by: steve <29133953+stevefan1999-personal@users.noreply.github.com>
Signed-off-by: steve <29133953+stevefan1999-personal@users.noreply.github.com>
@oplik0
Copy link
Contributor

oplik0 commented Jul 25, 2022

Hmm... The image from this PR doesn't seem to work at all for me - I'm mostly getting permission errors:

nodebb-7-nodebb-1  | chmod: /opt/config: Operation not permitted
nodebb-7-nodebb-1  | chmod: /opt/config: Operation not permitted
nodebb-7-nodebb-1  | cp: can't create '/opt/config/package.json': Permission denied
nodebb-7-nodebb-1  | touch: /opt/config/package-lock.json: Permission denied
nodebb-7-nodebb-1  | ln: package.json: File exists
nodebb-7-nodebb-1  | ln: package-lock.json: File exists
[...]
nodebb-7-nodebb-1  | npm ERR! code EACCES
nodebb-7-nodebb-1  | npm ERR! syscall open
nodebb-7-nodebb-1  | npm ERR! path /usr/src/app/package-lock.json
nodebb-7-nodebb-1  | npm ERR! errno -13
nodebb-7-nodebb-1  | npm ERR! Error: EACCES: permission denied, open '/usr/src/app/package-lock.json'
[...]
nodebb-7-nodebb-1  | Error: EACCES: permission denied, copyfile '/usr/src/app/install/package.json' -> '/usr/src/app/package.json'

The ln error is caused by ln -s attempting to run on every container boot which will fail if these files are already there. It can be remediated simply by adding the -f flag (so that part of entrypoint would look like this:

ln -fs $CONFIG_DIR/package.json package.json
ln -fs $CONFIG_DIR/package-lock.json package-lock.json

For a dirty fix of the permission errors I even ran as root and found another issue, and I 100% blame NPM for it. What is it?
NPM v7 won't work with NodeBB without ssh keys.
Why? It appears that something in NodeBB dependency tree depends on git+https://github.com/Mango/emitter.git (more specifically, this something: https://github.com/Mango/slideout).

So, logically, what protocol should this URL starting with git+https:// use? According to NPM, it's obviously SSH. Which means that using this kind of specifiers for dependencies now requires having SSH keys set up to download them, even if they are public.

The "workaround" is to use git+https://git@github.com/Mango/emitter.git which would require NodeBB to fork the slideout package.

Oh, and the issue in NPM repo has been open since last february: npm/cli#2610

Signed-off-by: steve <29133953+stevefan1999-personal@users.noreply.github.com>
…ill no permission on config dir

Signed-off-by: steve <29133953+stevefan1999-personal@users.noreply.github.com>
@oplik0
Copy link
Contributor

oplik0 commented Jul 26, 2022

It still seems the permissions for the default config directory are broken, I'm still getting

nodebb-nodebb-1  | chmod: /opt/config: Operation not permitted
nodebb-nodebb-1  | panic: no write permission for /opt/config

I'm just running docker compose --profile mongo up without any additional arguments or changes to docker-compose.yml or the dockerfile. Same thing happens both on Linux and Windows hosts.

@stevefan1999-personal
Copy link
Contributor Author

It still seems the permissions for the default config directory are broken, I'm still getting

nodebb-nodebb-1  | chmod: /opt/config: Operation not permitted
nodebb-nodebb-1  | panic: no write permission for /opt/config

I'm just running docker compose --profile mongo up without any additional arguments or changes to docker-compose.yml or the dockerfile. Same thing happens both on Linux and Windows hosts.

Interesting. It seems like you are running the container as another user that the UID-GID mapping isn't cleanly remapped. I need to think about that as well, since it could mean one of your upper directory does not give permission to that location (far as I remember it is chained, so if you can't the top/"leftmost" directory, the rest won't as well)

@julianlam
Copy link
Member

I will defer to @oplik0's opinion on merging this wrt technical details.

However, I noticed that you updated the readme to include information about the Docker config. That's great! I do want to move that out to a dedicated page on the documentation portal instead.

So I will do that and then merely link out to it from the readme, just like the installation/upgrade sections.

julianlam added a commit to NodeBB/docs that referenced this pull request Aug 14, 2023
re: NodeBB/NodeBB#8704, this commit takes the changes to README.md and includes them in a new page in the documentation instead.
@julianlam
Copy link
Member

This is now done, NodeBB/docs#78 will need to be merged at the same time for best effect 🪄

Copy link
Contributor

@oplik0 oplik0 left a comment

Choose a reason for hiding this comment

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

This time I actually found how to fix the issues I ran into, so with just a few changes I'm all for this. It's far better than current solution, both by optimizing some parts of the image by using alpine, and by greatly improving usability with the volumes.

@@ -54,6 +54,7 @@ jobs:
with:
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64
Copy link
Contributor

Choose a reason for hiding this comment

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

This was already changed, see line 60 - I guess I didn't notice this was also a part of this PR

mkdir -p $CONFIG_DIR
chmod -fR 760 $CONFIG_DIR

if [[ ! -w $CONFIG_DIR/package.json ]]; then
Copy link
Contributor

@oplik0 oplik0 Aug 14, 2023

Choose a reason for hiding this comment

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

This actually returns true if $CONFIG_DIR/package.json doesn't exist. You need to check the directory itself here (just if [[ ! -w $CONFIG_DIR ]])

Fixing this resolves my issue from #8704 (comment) - though chmod still throws an error

export SETUP="${SETUP:-}"

mkdir -p $CONFIG_DIR
chmod -fR 760 $CONFIG_DIR
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd redirect the errors from this to /dev/null - volumes on at least some platforms mount owned by root, making this fail despite the folder still being writable.

Though I'd also add a comment explaining this to help debugging. E.g.

# If the folder is mounted as a volume this can fail, the check below is to ensure there is still write access
chmod -fR 760 $CONFIG_DIR 2> /dev/null

ln -fs $CONFIG_DIR/package.json package.json
ln -fs $CONFIG_DIR/package-lock.json package-lock.json

npm install --only=prod
Copy link
Contributor

Choose a reason for hiding this comment

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

npm moved to --omit instead of --only for some reason, so this throws a deprecation warning. So this should now be

npm install --omit=dev

(note that this is from npm >=8, which became the default in Node 16, so it's currently in all supported node versions)

Copy link
Contributor

@oplik0 oplik0 Aug 14, 2023

Choose a reason for hiding this comment

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

Correction: also add --no-optional because sass-embedded currently doesn't support alpine Linux (it might soon-ish - it appears dart finally added support for musl builds a few months ago)
But npm doesn't catch this issue on the optional dependency because it only seems to look for generic os/architecture.

And sass removed musl-specific error handling in a refactor for some reason

@@ -14,28 +14,28 @@ USER node
RUN npm install --omit=dev


FROM node:lts
FROM node:lts-alpine3.16
Copy link
Contributor

Choose a reason for hiding this comment

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

This has been held up so long that 2 alpine versions came out, so it should be 3.18 now :)

@oplik0
Copy link
Contributor

oplik0 commented Sep 18, 2023

@stevefan1999-personal I just wanted to ask if you're still interested in pursuing this PR (I think all that's left are the fixes from stevefan1999-personal#71 and possibly rebasing to develop, but I'm not sure how important that is), or if that's not the case if you'd be fine with me making a new PR based on your work here?

@stevefan1999-personal
Copy link
Contributor Author

@oplik0 You can take over some of it. You can choose what was essential first. I will split this PR into different parts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

10 participants