Crissium / SilverDict

Web-Based Alternative to GoldenDict
https://silverdict.lecoteauverdoyant.co.uk
GNU General Public License v3.0
145 stars 19 forks source link

Failure to deploy Docker container on Synology NAS #13

Closed virgilinojuca closed 4 months ago

virgilinojuca commented 4 months ago

Hi! I stumbled with this project by chance, and this is exactly what I had been hoping existed! I had been wanting to be able to self-host my GoldenDict collection. Thanks for putting this together; the demo looks very good.

However, I'm having trouble installing this in a Synology NAS using Docker. I'm using the ready-built image from Docker Hub, so I put together the following compose file:

version: '3.9'
services:
  silverdict:
    image: mathdodger/silverdict:latest
    container_name: SilverDict
    restart: unless-stopped
    user: 1029:100 # "docker" user:"users" usergroup
    ports:
      - 2628:2628
    volumes:
      - /volume1/Docker/SilverDict/data/:/home/silverdict/.silverdict/ # Default folder
      - /volume1/Docker/SilverDict/cache/:/home/silverdict/.cache/SilverDict/ # Optional mounting of cache
      - /volume1/Dictionaries/_New dictionary library/:/dictionaries/ # Dictionary library; to be configured later

But I'm getting a few errors on the log, most notably about permission on certain folders. But I'm sure the user I defined has read and write permissions on the mounted folders.

The folowing is the logfile:

Crissium commented 4 months ago

SilverDict tries to find the cache (~/.cache/SilverDict) and settings (~/.silverdict) directories in the home of the user running it, and if they don't exist, create them. Looks like the home directory here is resolved to /? I have limited experience with Docker but I suspect that it is caused by the line user: 1029:100. In the Dockerfile we have defined a user silverdict to run the app.

Could you try removing the line and see if it works now?

virgilinojuca commented 4 months ago

I actually had added that line earlier, in hope it would fix the problem I was having.

Here is the log without specifying the user in the compose file:

đź“„ SilverDict2024-04-23.log

virgilinojuca commented 4 months ago

I even tried creating the folder source manually in /volume1/Docker/SilverDict/data/, and adding a StarDict dictionary both inside it and outside it. I got the same result. I checked the permissions to the folder and there is read&write permission to administrator users (in theory, this container is now running as root).

Crissium commented 4 months ago

To be honest I am now completely confused. Judging from the log, SilverDict is being run as the silverdict user, since the home directory is correctly resolved to /home/silverdict, but it is lacking write permission to ~/.silverdict??

I checked the permissions to the folder and there is read&write permission to administrator users (in theory, this container is now running as root).

Actually read & write permissions should be granted to the silverdict user. It should not be run as root.

I dug up the following command from my bash history that I used to test my image.

docker run -dp 127.0.0.1:2628:2628 --mount type=bind,src=/home/ellis/Workspace/SilverDict/Docker/Testing/data,target=/home/silverdict/.silverdict/ mathdodger/silverdict

I did not specify any user at all and it worked smoothly and without any permission issues. Is this any help?

virgilinojuca commented 4 months ago

But the host won't always have the same user names as the container, so it's the silverdict user UID (probably 1000) that is going to be used on the host machine. But my NAS doesn't have a user assigned to the UID 1000.

I'm going to play around with the option to build the image from source—which I have never done—and see if I'll get better results.

Crissium commented 4 months ago

But the host won't always have the same user names as the container, so it's the silverdict user UID (probably 1000) that is going to be used on the host machine. But my NAS doesn't have a user assigned to the UID 1000.

My understanding of Docker is far from perfect but I don't think you could run an application inside the container as an user on the host OS?

@simonmysun As the original author of the Dockerfile, could you take a look at this if it is not too much trouble? I haven't got a clue what is happening. Thank you very much!

simonmysun commented 4 months ago

Hi,

This issue arises because the ~/.cache folder inside the container and the /path/to/dictionaries folder on the host are created with volume binds, which makes them both owned by root. (To be honest, I don't think we need ~/.cache to be persistent. Could you please elaborate on this?)

To properly bring the service up, you need to ensure that the two binded paths are accessible and writable ~by the UID and GID you specified~. When Docker has to create the paths (e.g., when the paths don't exist), they will be owned by root:root with 755 permission bits, as far as I've observed. So, you need to either change their owner to 1000:1000 ~or whichever UID and GID you specified~, or modify their permission bits to be writable.

I had the UID problem in mind and should have fixed it or mentioned it in the documentation, but I was overwhelmed with a lot of stuff in my life after I arrived home. Sorry about that.

I don't have a perfect solution in mind. After some research, I think we have these options:

What do you suggest?

In any case, we need to clarify in the documentation that it is recommended to create the binded paths before letting Docker create them. If they are already created by Docker or the host user is not the 1000:1000, users need to modify their permission bits or ownership.

Edit: sorry I didn't notice that if you write user: 1029:100 in docker compose file, the silverdict is trying to access /.cache/ and /.dictionaries/. You can try to bind these two paths and set proper permissions.

Crissium commented 4 months ago

Thanks a lot for your detailed explanation!

This issue arises because the ~/.cache folder inside the container and the /path/to/dictionaries folder on the host are created with volume binds, which makes them both owned by root. When Docker has to create the paths (e.g., when the paths don't exist), they will be owned by root:root with 755 permission bits, as far as I've observed. In any case, we need to clarify in the documentation that it is recommended to create the binded paths before letting Docker create them. If they are already created by Docker or the host user is not the 1000:1000, users need to modify their permission bits or ownership.

Does it mean that, in this case, when /volume1/Docker/SilverDict/cache/ is not found on the host, it gets created by Docker and ordinary users inside the container lack permissions to it?

To be honest, I don't think we need ~/.cache to be persistent. Could you please elaborate on this?

No it does not need to persist. If you peep into the cache directory, you will find it is populated with the following data:

All could be recreated fast enough. Still it undoubtedly speeds up startup and saves some extra work when the user looks up the same words.

By the way now I remembered that when I was testing the image myself, I first created the directories myself, then started the container. Presumably that was why I never encountered this issue.

I had the UID problem in mind and should have fixed it or mentioned it in the documentation, but I was overwhelmed with a lot of stuff in my life after I arrived home. Sorry about that.

No need to worry! I deeply appreciate all your kind help in bringing SilverDict to Docker and after this issue is resolved successfully I'll mention the problem in the documentation myself. Well perhaps lettting the user set permission bits manually is too tall an order since Docker is supposed to be deployed with one command…

Edit: So, in short, I think the solution to the OP's problem is to first create /volume1/Docker/SilverDict/data/ and /volume1/Docker/SilverDict/cache/ manually.

Edit again:

Running the server as root inside the container and letting the user to operate dictionaries with root role.

Perhaps this is the best course of action after all? Since it is containerised there's no security concerns. And that is what I had originally done when deploying SilverDict to Okteto anyway. Later I'll look into how to modify the Dockerfile.

simonmysun commented 4 months ago

Does it mean that, in this case, when /volume1/Docker/SilverDict/cache/ is not found on the host, it gets created by Docker and ordinary users inside the container lack permissions to it?

Yes.

Still it undoubtedly speeds up startup and saves some extra work when the user looks up the same words.

Indeed. It worths when it save time for looking up the same words.

Perhaps this is the best course of action after all? Since it is containerised there's no security concerns. And that is what I had originally done when deploying SilverDict to Okteto anyway. Later I'll look into how to modify the Dockerfile.

It is recommended to run as non-root user (ref:https://docs.docker.com/develop/develop-images/instructions/#user) though personally I find it not necessary. If we don't insist on this everything will be easier. I would also vote on this option for the simplicity.

So, in short, I think the solution to the OP's problem is to first create /volume1/Docker/SilverDict/data/ and /volume1/Docker/SilverDict/cache/ manually.

I'm afraid OP's UID and GID is different and needs to chmod a+rw the two paths. We didn't see this problem because we were running as 1000:1000 which is the same as the user silverdict inside the container. I'm not sure if I'm getting the user namespace mechanism in docker right. I need to read more documents...

virgilinojuca commented 4 months ago

Edit: sorry I didn't notice that if you write user: 1029:100 in docker compose file, the silverdict is trying to access /.cache/ and /.dictionaries/. You can try to bind these two paths and set proper permissions.

Yep, mounting these two paths works fine with the user: 1029:100. Thanks!

Crissium commented 4 months ago

Yep, mounting these two paths works fine with the user: 1029:100. Thanks!

Congratulations! Aproveite!

So, to resolve such issues once and for all, I made the following changes to the Dockerfile. Well the only inconvenience is that I cannot copy dictionaries to the source directory as 1000:1000 (on the host OS). Otherwise it works well.

# Stage 1: Frontend Builder
FROM node:lts-alpine3.19@sha256:ef3f47741e161900ddd07addcaca7e76534a9205e4cd73b2ed091ba339004a75 as frontend-builder

WORKDIR /silverdict/client

# Copy only package.json and yarn.lock to leverage Docker cache layer
COPY ./client/package.json ./client/yarn.lock ./
RUN yarn install --frozen-lockfile

# Copy the entire client directory
COPY client ./

# Build the frontend
RUN yarn build

# Stage 2: Production Environment
FROM alpine:3.19.1@sha256:15c46ced65c6abed6a27472a7904b04273e9a8091a5627badd6ff016ab073171

ARG VERSION="1.1.5"
ARG ENABLE_FULL_TEXT_SEARCH=""
ARG ENABLE_MORPHOLOGY_ANALYSIS=""
ARG ENABLE_CHINESE_CONVERSION=""

ENV HOST="0.0.0.0"
ENV PORT="2628"

ENV PYTHONUNBUFFERED=1
ENV VIRTUAL_ENV=/opt/venv
ENV PATH="$VIRTUAL_ENV/bin:$PATH"

LABEL org.opencontainers.image.title="SilverDict"
LABEL org.opencontainers.image.description="Web-Based Alternative to GoldenDict"
LABEL org.opencontainers.image.version="${VERSION}"
LABEL org.opencontainers.image.authors="Yi Xing <blandilyte@gmail.com>"
LABEL org.opencontainers.image.url="https://crissium.github.io/SilverDict"
LABEL org.opencontainers.image.source="https://github.com/Crissium/SilverDict"
LABEL org.opencontainers.image.licenses="GPL-3.0-or-later"

WORKDIR /silverdict/server

# RUN adduser -D silverdict

# COPY --chown=silverdict:silverdict ./server/requirements.txt /silverdict/server/requirements.txt
COPY ./server/requirements.txt /silverdict/server/requirements.txt

# Install dependencies
RUN apk update && \
  apk add --no-cache python3 lzo py3-yaml py3-xxhash py3-flask py3-flask-cors py3-waitress py3-requests ${ENABLE_FULL_TEXT_SEARCH:+xapian-bindings-python3 py3-lxml} ${ENABLE_MORPHOLOGY_ANALYSIS:+libhunspell} && \
  apk add --no-cache --virtual .build-deps python3-dev py3-pip gcc g++ lzo-dev ${ENABLE_MORPHOLOGY_ANALYSIS:+hunspell-dev} ${ENABLE_CHINESE_CONVERSION:+make cmake doxygen} && \
  python3 -m venv $VIRTUAL_ENV --system-site-packages && \
  pip install --no-cache-dir -r requirements.txt && \
  if [ "x$ENABLE_MORPHOLOGY_ANALYSIS" = "xtrue" ]; then \
    ln -s /usr/include/hunspell/* /usr/include/ && \
    ln -s /usr/lib/libhunspell-*.so /usr/lib/libhunspell.so && \
    pip install hunspell; \
  fi && \
  if [ "x$ENABLE_CHINESE_CONVERSION" = "xtrue" ]; then \
    wget https://github.com/BYVoid/OpenCC/archive/refs/tags/ver.1.1.7.tar.gz && \
    tar xzf ver.1.1.7.tar.gz && \
    cd OpenCC-ver.1.1.7 && \
    pip install wheel && \
    make python-build && \
    make python-install && \
    pip uninstall -y wheel && \
    cd .. && \
    rm -rf OpenCC-ver.1.1.7 ver.1.1.7.tar.gz; \
  fi && \
  apk del .build-deps

# Copy server and built frontend from the frontend-builder stage
# COPY --chown=silverdict:silverdict ./server /silverdict/server
COPY ./server /silverdict/server
# COPY --from=frontend-builder --chown=silverdict:silverdict /silverdict/client/build /silverdict/server/build
COPY --from=frontend-builder /silverdict/client/build /silverdict/server/build

# Switch to non-root user
# USER silverdict

# Expose the required port
EXPOSE "${PORT}/tcp"

# Entry point for the application
ENTRYPOINT python server.py "${HOST}:${PORT}"
version: '3.8'
services:
  silverdict:
    build: 
      context: .
      dockerfile: Dockerfile
      args:
        ENABLE_FULL_TEXT_SEARCH: 'true'
        ENABLE_MORPHOLOGY_ANALYSIS: 'true'
        ENABLE_CHINESE_CONVERSION: 'true'
    volumes:
      - /home/ellis/Workspace/SilverDict/Docker/NewRoot/data/:/root/.silverdict/
      - /home/ellis/Workspace/SilverDict/Docker/NewRoot/cacge/:/root/.cache/SilverDict/ # Optional mounting of cache
    restart: unless-stopped
    ports:
      - 2628:2628

Could you please review before I commit the changes? @simonmysun

simonmysun commented 4 months ago

LGTM. You can remove the unnecessary comments and also mask you personal information (e.g /home/ellis/)

Crissium commented 4 months ago

LGTM. You can remove the unnecessary comments and also mask you personal information (e.g /home/ellis/)

Thanks! I'll just leave the Docker compose file as is then.