Building Docker image as non root user - docker

New here, was wondering if someone had experience with building images as non root user?
I am building Kotlin project, (2 step build) and my goal is now to build it as non root user. Here is what my Dockerfile looks like. Any help would be appreciated:
# Build
FROM openjdk:11-jdk-slim as builder
# Compile application
WORKDIR /root
COPY . .
RUN ./gradlew build
FROM openjdk:11-jre-slim
# Add application
COPY --from=builder /root/build/libs/*.jar ./app.jar
# Set the build version
ARG build_version
ENV BUILD_VERSION=$build_version
COPY docker-entrypoint.sh /
RUN chmod 777 /docker-entrypoint.sh
CMD /docker-entrypoint.sh

In order to use Docker, you don't need to be a root user, you just need to be inside of the docker user group.
On Linux:
If there is not already a docker group, you can create one using the command sudo groupadd docker.
Add yourself and any other users you would like to be able to access docker to this group using the command sudo usermod -aG docker [username of user].
Relog, so that Linux can re-evaluate user groups.
If you are not trying to run the command as root, but rather want to run the container as non-root, you can use the following DOCKERFILE contents (insert after FROM but before anything else.)
# Add a new user "john" with user id 8877
RUN useradd -u 8877 john
# Change to non-root privilege
USER john

Related

Variables in Dockerfile don't seem to be recognized?

I am building an image using Dockfile. I would like to set the Username of the container via the command line to avoid permission issues.
The Dockfile is shown below, I used the variables of USER_NAME, GROUP_ID. But when I build, the problem keeps appearing.
The error is: groupadd: option '--gid' requires an argument
I'm guessing that both ${GROUP_ID} and ${USER_NAME} are recognized as empty strings, but shouldn't they be assigned values ​​when the container is created?
I've googled a few examples and based on the examples, I don't quite see where the problem is?
Please help me!
Thanks!
FROM matthewfeickert/docker-python3-ubuntu:latest
ARG USER_NAME
ARG USER_ID
ARG GROUP_ID
RUN groupadd -r --gid ${GROUP_ID} ${USER_NAME}
RUN useradd --no-log-init -r -g ${GROUP_ID} -u ${USER_ID} ${USER_NAME}
USER ${USER_NAME}
WORKDIR /usr/local/src
When you run the container, you can specify an arbitrary user ID with the docker run -u option.
docker run -u 1003 ... my-image
This doesn't require any special setup in the image. The user ID won't exist in the container's /etc/passwd file but there aren't really any consequences to this, beyond some cosmetic issues with prompts in interactive debugging shells.
A typical use of this is to give your container access to a bind-mounted data directory:
docker run \
-e DATA_DIR=/data \
-v "$PWD/app-data:/data" \
-u $(id -u) \
... \
my-image
I'd generally recommend not passing a specific user ID into your image build. This would make the user ID "baked in", and if someone with a different host uid wanted to run the image, they'd have to rebuild it.
It's often a good practice to set up some non-root user, but it doesn't matter what its user ID is so long as it's not zero. In turn, it's also typically a good practice to leave most of your application source code owned by the root user so that the application can't accidentally overwrite itself.
FROM matthewfeickert/docker-python3-ubuntu:latest
# Create an arbitrary non-root user; we don't care about its uid
# or other properties
RUN useradd --system user
# Still as root, do the normal steps to install and build the application
WORKDIR /app
COPY requirements.txt ./
RUN pip install -r requirements.txt
COPY ./ ./
# Still as root, make sure the data directory exists
ENV DATA_DIR=/data
RUN mkdir "$DATA_DIR" && chown user "$DATA_DIR"
# VOLUME ["/data"]
# Normal metadata to run the container, only switching users now
EXPOSE 5000
USER user
CMD ["./app.py"]
This setup will still work with the extended docker run command shown initially: the docker run -v option will cause the container's /data directory to take on its numeric uid owner from the host, which (hopefully) matches the docker run -u uid.
You can pass the build args as shown below.
docker build --build-arg USER_NAME=test --build-arg USER_ID=805 --build-arg GROUP_ID=805 -t tag1 .
Also, as a best practice consider adding default vales to the args. So if the user doesn't specify the args the default values will be picked.

Kafka-connect docker image built as appuser - how to build it as root?

I am trying to build a Kafka-Connect image in Docker:
FROM confluentinc/cp-kafka-connect
RUN confluent-hub install --no-prompt wepay/kafka-connect-bigquery:1.6.1
RUN confluent-hub install --no-prompt confluentinc/connect-transforms:latest
RUN mkdir -p /usr/share/landoop-plugins
COPY kafka-connect-redis-1.2.2-2.1.0-all.jar /usr/share/landoop-plugins/
but it runs as appuser
Step 4/4 : RUN id
---> Running in d2094f6336a7
uid=1000(appuser) gid=1000(appuser) groups=1000(appuser)
so if I want for example
RUN mkdir -p /usr/share/landoop-plugins
it stops because of root privilages:
mkdir: cannot create directory '/usr/share/landoop-plugins': Permission denied
The command '/bin/sh -c mkdir -p /usr/share/landoop-plugins' returned a non-zero code: 1
I can add USER root at the beginning of Dockerfile:
Step 3/15 : RUN id
---> Running in 6255e2e7ff81
uid=0(root) gid=0(root) groups=0(root)
but then if I ran the container, I am logged as appuser which causes problems with permissions:
[appuser#connect ~]$.
Actually, in source image
confluentinc/cp-kafka-connect:6.0.0
there is a layer USER appuser so my question is how can I build my image as root and then login as root and do not use appuser user. I've tried and USER root does not help.
Is it somehow connected with groups?
groups
gignac sudo docker
I tried docker build and sudo docker build as well
I do something similar, when I need to create my own plugin to Kafka Connect but I don't exactly do it as root.
Simply I put my jars in a place I have permission to write and just configure the plugins Environment Setting
something like this:
FROM confluentinc/cp-kafka-connect:5.5.2
ENV CONNECT_PLUGIN_PATH='/usr/share/java,/usr/share/confluent-hub-components'
COPY converter/* /usr/share/java/kafka-serde-tools/
COPY format/* /usr/share/java/kafka-connect-storage-common/
would this not work?
For installing packages and troubleshooting, we would require root user. Using version 5.5.3 does the trick wherein the APPUSER is not created and root is loaded by default.
The version can be verified by
http://localhost:8083/connectors
which provides the version details.

Dockerfile - Creating non-root user almost doubles the image size

Created an application image on top of ubuntu. The application requires a non-root user.
I am able to create the image and get it working but the RUN statement for creating new user significantly increases the size of this image.
Dockerfile snippet:
## create newuser and give correct permissions to home directory
RUN useradd newuser --create-home --shell /bin/bash && \
echo 'newuser:newpassword' | chpasswd && \
chown -R newuser:newuser /home/newuser && \
chmod 755 /home/newuser
USER newuser
WORKDIR /home/newuser
Is there a better way to create a new user?
Thinking about alternate approaches, wonder if one uses multi-stage build to create this new user and then use copy --from to get relevant files in the final build. Not sure what those files would be.
Don't chown the directory; leave root owning it. You also shouldn't need to set a shell, or a password, or create a home directory; none of these things will be used in normal operation.
I'd suggest creating the user towards the start of the Dockerfile (it is fairly fixed and so this step can be cached) but only switching USER at the very end of the file, when you're setting up the metadata for how to run the container.
A Node-based example:
FROM node:lts # debian-based
# Create the non-root user up front
RUN adduser --system --group --no-create-home newuser
# Copy and build the package as usual
WORKDIR /app
COPY package.json yarn.lock .
RUN yarn install
COPY . .
RUN yarn build
# Now the application is built
# And root owns all the files
# And that's fine
# Say how to run the container
EXPOSE 3000
USER newuser
CMD yarn start
Having root owning the files gives you a little extra protection in case something goes wrong. If there's a bug that allows files in the container to be overwritten, having a different user owning those files prevents the application code or static assets from being inadvertently modified.
If your application needs to read or write files then you could create a specific directory for that:
# Towards the end of the file, but before the USER
RUN mkdir data && chown newuser data
This will let the operator mount some storage over the otherwise-empty directory. This is the only thing that has the newly created user ID in it at all, so if the storage comes with its own owner it shouldn't be an operational problem; you need to also specify the matching user ID at container startup time.
docker run -u $(id -u) -v $PWD/data:/app/data ...

Setup different user permissions on files copied in Dockerfile

I have this Dockerfile setup:
FROM node:14.5-buster-slim AS base
WORKDIR /app
FROM base AS production
ENV NODE_ENV=production
RUN chown -R node:node /app
RUN chmod 755 /app
USER node
... other copies
COPY ./scripts/startup-production.sh ./
COPY ./scripts/healthz.sh ./
CMD ["./startup-production.sh"]
The problem I'm facing is that I can't execute ./healthz.sh because it's only executable by the node user. When I commented out the two RUN and the USER commands, I could execute the file just fine. But I want to enforce the executable permissions only to the node for security reasons.
I need the ./healthz.sh to be externally executable by Kubernetes' liveness & rediness probes.
How can I make it so? Folder restructuring or stuff like that are fine with me.
In most cases, you probably want your code to be owned by root, but to be world-readable, and for scripts be world-executable. The Dockerfile COPY directive will copy in a file with its existing permissions from the host system (hidden in the list of bullet points at the end is a note that a file "is copied individually along with its metadata"). So the easiest way to approach this is to make sure the script has the right permissions on the host system:
# mode 0755 is readable and executable by everyone but only writable by owner
chmod 0755 healthz.sh
git commit -am 'make healthz script executable'
Then you can just COPY it in, without any special setup.
# Do not RUN chown or chmod; just
WORKDIR /app
COPY ./scripts/healthz.sh .
# Then when launching the container, specify
USER node
CMD ["./startup-production.sh"]
You should be able to verify this locally by running your container and manually invoking the health-check script
docker run -d --name app the-image
# possibly with a `docker exec -u` option to specify a different user
docker exec app /app/healthz.sh && echo OK
The important thing to check is that the file is world-executable. You can also double-check this by looking at the built container
docker run --rm the-image ls -l /app/healthz.sh
That should print out one line, starting with a permission string -rwxr-xr-x; the last three r-x are the important part. If you can't get the permissions right another way, you can also fix them up in your image build
COPY ./scripts/healthz.sh .
# If you can't make the permissions on the original file right:
RUN chmod 0755 *.sh
You need to modify user Dockerfile CMD command like this : ["sh", "./startup-production.sh"]
This will interpret the script as sh, but it can be dangerous if your script is using bash specific features like [[]] with #!/bin/bash as its first line.
Moreover I would say use ENTRYPOINT here instead of CMD if you want this to run whenever container is up

Reuse user in multi-stage Dockerfile

As you know, for security reasons, isn't good to use root user execept if you need it. I have this Dockerfile that I use with multi-stage steps
FROM golang:latest AS base
WORKDIR /usr/src/app
# Create User and working dir
RUN addgroup --gid 42000 app
RUN useradd --create-home --uid 42000 --gid app app
RUN chown -R app:app /usr/src/app
RUN chmod 755 /usr/src/app
# Compile stage based on Debian
FROM base AS builder
USER app
# Copy form computer to current WORKDIR container
COPY . .
# Exit immediately if a command exits with a non-zero status
RUN set -xue && \
make go-build-linux
# Final stage
FROM debian:latest
USER app
EXPOSE 14001
RUN apt-get update && \
apt-get install -y ca-certificates
WORKDIR /usr/src/app
COPY --from=builder /usr/src/app/server .
CMD ["./server"]
The problem is that I'm trying to reuse the user in all steps but seems to be that the user scope is by stage and I don't know how to reuse it.
Do you know how I can reuse a user in a multi-stage Dockerfile and try to avoid to use root user from Dockerfile?
Thanks!
TL;DR;
It is not possible to re-use the same user in multiple stages of the docker build without re-creating the user (same UID and GID at least) in each stage as each FROM is starting from a clean slate FROM image in which a user UID=42000 and GID=42000 is unlikely to already exist.
I am not aware of any recommendation against building as the root user inside a container. It is recommended to run services as unprivileged users however certain containers processes must be run as the root user (i.e. sshd):
The best way to prevent privilege-escalation attacks from within a container is to configure your container’s applications to run as unprivileged users. For containers whose processes must run as the root user within the container, you can re-map this user to a less-privileged user on the Docker host. The mapped user is assigned a range of UIDs which function within the namespace as normal UIDs from 0 to 65536, but have no privileges on the host machine itself.
Tip: The Haskell Dockerfile Linter will complain if the last user is root which you can configure as a git pre-commit hook to catch things like that before committing teh codez.

Resources