Add env var for `WORKING_DIR` config item
In order to run a docker image as a non-root user, there needs to be a method to update permissions on a defined chainstate dir.
typically, a config with a stateful chainstate dir will be defined like:
[node]
working_dir = "/data/chainstate" # defaults to: /tmp/stacks-node-[0-9]*
...
but, the container (when runnning as non-root) can't know about this directory before hand. there are a few options to resolve:
- entrypoint script to parse the config, retrieve the working_dir value and then perform any necessary
mkdirorchownon the directory. this would require hardcoding how the container is run though, via an entrypoint and is less than ideal. - env var that can expose the
working_dirto the container. would still require an entrypoint script, but would make it possible to perform the necessary filesystem changes before starting the binary.
diff --git a/Dockerfile b/Dockerfile
index ca03fa3ac6..4648e36c47 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -8,9 +8,15 @@ WORKDIR /src
COPY . .
RUN mkdir /out
RUN rustup toolchain install stable
-RUN cargo build --features monitoring_prom,slog_json --release
+RUN cargo build --features monitoring_prom,slog_json --release --bin stacks-node --bin stacks-signer --bin stacks-inspect
RUN cp -R target/release/. /out
FROM debian:bookworm-slim
+ARG UID=101
+ARG GID=101
+ARG USER=stacks
+RUN groupadd --gid ${GID} ${USER} \
+ && useradd -M --no-log-init -u ${UID} -g ${GID} ${USER}
COPY --from=build /out/stacks-node /out/stacks-signer /out/stacks-inspect /bin/
+USER ${USER}
CMD ["stacks-node", "mainnet"]
using the above dockerfile diff, it's possible to run the binary as a new user stacks and the chainstate in /tmp/stacks... is created on startup. the problem arises when using a config where working_dir is defined:
ERRO [1758915504.902674] [stacks-node/src/main.rs:255] [main] Process abort due to thread panic: panicked at stacks-node/src/node.rs:322:25:
Error while opening chain state at path /data/chainstate/mocknet/chainstate: DBError(IOError(Os { code: 13, kind: PermissionDenied, message: "Permission denied" }))
Open to other suggestions as well, but i think at a minimum we need to allow setting the chainstate dir via env var.
tinkering a bit, but there may be an easier way than modifying what looks like a lot of files in stacks-core.
the command must follow a pattern (when using a config file) like --config /path/to/config.toml.
i think we could simply parse the command in an entrypoint script, retrieving the config file and then parsing out if there is a working_dir k/v. if there is, create and chown before exec'ing the binary (this would also work stacks-signer i think).
something like this is what i'm thinking:
#!/usr/bin/env bash
echo "received cmd: '$@'"
ARGS=($@) # store as an array
config=""
for i in $(seq 1 $#); do
echo "args $i: ${ARGS[$i]}"
if [ "${ARGS[$i]}" == "--config" ]; then
echo "match at element $i. config file at element $(( i + 1))"
config=${ARGS[$(( i + 1 ))]}
break
fi
done
echo "config:$config"
if [ -z "$config" ]; then
echo "config flag not passed"
exit 1
fi
# config is defined
echo "config: $config"
if ! [ -f "$config" ]; then
echo "missing config file"
exit
fi
# note: signer uses db_path
working_dir=$(grep "working_dir" ${config} | cut -f2 -d "=" | tr -d '"' | xargs)
echo "working_dir defined as: $working_dir"
which produces:
$ bash arg.sh stacks-node start
received cmd: 'stacks-node start'
args 1: start
args 2:
config:
config flag not passed
$ bash arg.sh stacks-node start --config /stacks-blockchain/conf/mainnet/config.toml
received cmd: 'stacks-node start --config /stacks-blockchain/conf/mainnet/config.toml'
args 1: start
args 2: --config
match at element 2. config file at element 3
config:/stacks-blockchain/conf/mainnet/config.toml
config: /stacks-blockchain/conf/mainnet/config.toml
missing config file
$ bash arg.sh stacks-node start --config /stacks-blockchain/conf/mainnet/follower.toml
received cmd: 'stacks-node start --config /stacks-blockchain/conf/mainnet/follower.toml'
args 1: start
args 2: --config
match at element 2. config file at element 3
config:/stacks-blockchain/conf/mainnet/follower.toml
config: /stacks-blockchain/conf/mainnet/follower.toml
working_dir defined as: /stacks-blockchain
simple example, but i think it's a viable path vs updating stacks-core for that single env var.
I think the approach you describe makes sense (taking advantage of the stacks core "start" command) and it is likely the least intrusive.
Regarding container permissions: the container will need to start as root (or with sufficient privileges) so that the entrypoint script can prepare directories and adjust ownership as needed and then switch to the intended user/group just before starting the main process (common pattern for docker). This was probably implied by your solution but worth to make it explicit.
I think the approach you describe makes sense (taking advantage of the stacks core "start" command) and it is likely the least intrusive.
Regarding container permissions: the container will need to start as root (or with sufficient privileges) so that the entrypoint script can prepare directories and adjust ownership as needed and then switch to the intended user/group just before starting the main process (common pattern for docker). This was probably implied by your solution but worth to make it explicit.
Thanks for the feedback! yes, root to create/chown the chainstate dir is something i'm thinking about, so thanks for reminding me. looking at other projects and repos - i think i'm going to try https://github.com/tianon/gosu which is the dockerized way to do this and is used pretty widely for this type of scenario.