# A container for PyBitmessage daemon FROM ubuntu:bionic SHELL ["/bin/bash", "-exo", "pipefail", "-c"] # Install dependencies. update and install share a layer on purpose: split # across two, a cached update feeds install package lists that may be months # stale, and the install then fails or pulls something unintended. RUN apt-get update \ && apt-get install -yq --no-install-suggests --no-install-recommends \ build-essential libcap-dev libssl-dev \ python-all-dev python-msgpack python-pip python-setuptools \ git ## Do not use cache when building next layers of the image. ARG NOCACHE=0 WORKDIR /root/PyBitmessage RUN git clone https://github.com/Bitmessage/PyBitmessage . # Install RUN pip2 install jsonrpclib . # Raise the SQL-thread startup timeout from the stock 60 s. # # PyBitmessage kills the daemon outright if the SQL thread is not ready within # sql_timeout seconds (class_objectProcessor.py -> os._exit(1)). The startup # VACUUM of a messages.dat that has grown to a few hundred MB does not fit in # 60 s, and since the process dies mid-VACUUM lastvacuumtime is never updated, # so every later start retries the same doomed VACUUM and the node never comes # back. Measured: 26 s for a 264 MB database on an idle host, and the last # start that did survive used 36 s of the 60. # # The greps are load-bearing: the clone above is unpinned, so if upstream ever # moves or renames the constant, a silent no-op sed would ship an image that # looks fixed and is not. Fail the build instead. The .pyc is refreshed because # at runtime /usr/local is root-owned while the daemon runs as bitmessage, so a # stale one can only be recompiled to memory on every start. RUN f=/usr/local/lib/python2.7/dist-packages/pybitmessage/helper_sql.py \ && grep -q '^sql_timeout = 60$' "$f" \ && sed -i 's/^sql_timeout = 60$/sql_timeout = 600/' "$f" \ && grep -q '^sql_timeout = 600$' "$f" \ && rm -f "${f}c" \ && python -c "import py_compile; py_compile.compile('$f')" FROM ubuntu:bionic # 8442 is the XML-RPC API (keep it on loopback), 8444 the Bitmessage P2P port. # The daemon listens on both regardless; publishing 8444 is what makes the node # reachable for inbound peers. EXPOSE 8442/tcp EXPOSE 8444/tcp ENV USER_UID=2000 ENV USER_GID=2000 ENV HOME=/home/bitmessage ENV BITMESSAGE_HOME=${HOME} COPY --from=0 /usr/local/ /usr/local/ COPY ./docker/healthy_check.py /usr/local/bin/ COPY ./docker/seed_addr_gen.py /usr/local/bin/ COPY ./docker/watchdog.py /usr/local/bin/ COPY ./docker/run.sh /usr/local/bin/ # Install dependencies. gosu is gone with the last thing that needed it: the # container starts as the daemon's user and never changes user, so there is no # privilege to drop at run time. moreutils went with it -- nothing in this # repository ever called any of its tools. RUN apt-get update \ && apt-get install -yq --no-install-suggests --no-install-recommends python-setuptools \ && rm -rf /var/lib/apt/lists/* # Create a user RUN addgroup --gid $USER_GID bitmessage ;\ useradd --uid $USER_UID --gid $USER_GID --skel /dev/null --create-home --home-dir $HOME bitmessage WORKDIR ${HOME} # Generate default config RUN su bitmessage -c "pybitmessage -t" # keys.dat holds the API password and the node's private keys, and the daemon # writes it back at that mode anyway. Set here rather than on every start, # because on every start there would be no root to do it: a named volume takes # its first contents, and their ownership and modes, from the image. RUN chmod 600 keys.dat # Nothing here needs a setuid or setgid bit at run time, and every one of them # is a way back to root for a daemon that has been taken over -- the more so # under a caller that adds no-new-privileges, which leaves them as the one thing # that could still have raised it. Strip them all, then insist none is left, so # a package added here later cannot bring one back unnoticed. Last root step in # the file, and it has to be: after USER below there is no chmod to be had. RUN find / -xdev -type f -perm /6000 -exec chmod -s {} + \ && [ -z "$(find / -xdev -type f -perm /6000)" ] # The daemon's own user, from PID 1 onwards. Nothing in this image needs root # once it is built: the files it works on are its own, and the only two it # creates -- knownnodes.dat and messages.dat -- it creates in its home. That # also settles what a `docker exec` and the healthcheck below run as. # # The caller no longer has to hand any capability back after cap_drop: ALL. In # exchange, a bind mount over /home/bitmessage has to be owned by this uid; a # named volume, which is what this image is meant for, inherits it from the # image and needs nothing. USER bitmessage # One line, because a wrong USER is a whole class of surprise and this is where # it is cheapest to find out. RUN [ "$(id -u)" = "$USER_UID" ] && [ "$(id -g)" = "$USER_GID" ] CMD ["sh", "/usr/local/bin/run.sh"] ## Check PyBitmessage active network connections. ## The start period covers the startup VACUUM of messages.dat, which takes tens ## of seconds once the database reaches a few hundred MB; without it the ## container reports unhealthy for that whole window on every restart. HEALTHCHECK --retries=0 --interval=15s --start-period=180s \ CMD ["python", "/usr/local/bin/healthy_check.py"]