stacks-core icon indicating copy to clipboard operation
stacks-core copied to clipboard

Add env var for `WORKING_DIR` config item

Open wileyj opened this issue 3 months ago • 4 comments

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:

  1. entrypoint script to parse the config, retrieve the working_dir value and then perform any necessary mkdir or chown on the directory. this would require hardcoding how the container is run though, via an entrypoint and is less than ideal.
  2. 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.

wileyj avatar Sep 26 '25 19:09 wileyj

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).

wileyj avatar Sep 29 '25 19:09 wileyj

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.

wileyj avatar Sep 29 '25 21:09 wileyj

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.

fdefelici avatar Oct 02 '25 08:10 fdefelici

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.

wileyj avatar Oct 02 '25 15:10 wileyj