mhart/alpine-node

Best Practices for Non-root User

styfle opened this issue Β· 16 comments

First of all, thanks for creating and maintaining such a wonderful docker image! You're always so quick to update when a new version of node comes out πŸ’―. And now for my question...

In the official docker-node repo, there is a Best Practices doc that explains you should create a user instead of running as root.

# Add our user and group first to make sure their IDs get assigned consistently
RUN groupadd -r app && useradd -r -g app app 

Does alpine-node have a similar best practice?
What is the equivalent groupadd and useradd in Alpine?

mhart commented

Try adduser and addgroup – lemme know if that works

I think I figured it out. The -S flag will add to system group which is equivalent to -r in Ubuntu.

RUN addgroup -S app && adduser -S -G app app 

Should I add a best practices to this repo? Or are we close to getting alpine in the official docker node and just wait to add it there? See docker-node/pull/156.

mhart commented

Yeah, let's wait for the official image – I'm sure they'll be getting that in soon

Thanks for highlighting this though and finding the appropriate command line options!

Hey @styfle just wanted to stop by and say thank you for sharing this! I'm thinking about curating a list of best practices for docker / alpine because I'm personally having a hard time making the right decisions.

@peterpme I am working with the official docker-node image to get these practices updated.
See linked PR 99 above.

adduser isn't available on Alpine out of the box?

mhart commented

@ORESoftware yeah it is – what version you running?

$ docker run alpine:3.6 adduser
BusyBox v1.26.2 (2017-06-11 06:38:32 GMT) multi-call binary.

Usage: adduser [OPTIONS] USER [GROUP]

Create new user, or add USER to GROUP

	-h DIR		Home directory
	-g GECOS	GECOS field
	-s SHELL	Login shell
	-G GRP		Add user to existing group
	-S		Create a system user
	-D		Don't assign a password
	-H		Don't create home directory
	-u UID		User id
	-k SKEL		Skeleton directory (/etc/skel)

$ docker run alpine:3.4 adduser
BusyBox v1.24.2 (2017-01-18 14:13:46 GMT) multi-call binary.

Usage: adduser [OPTIONS] USER [GROUP]

Create new user, or add USER to GROUP

	-h DIR		Home directory
	-g GECOS	GECOS field
	-s SHELL	Login shell
	-G GRP		Add user to existing group
	-S		Create a system user
	-D		Don't assign a password
	-H		Don't create home directory
	-u UID		User id
	-k SKEL		Skeleton directory (/etc/skel)

For some reason using the command @styfle posted, my user stayed at the nogroup.
I tested a lot and this worked:

RUN addgroup -S myawesomegroup
RUN adduser -S myawesomeuser -G myawesomegroup

:)

For latest Alpine image, the 3.7 the correct command is:

RUN addgroup -g 1000 -S username && \
    adduser -u 1000 -S username -G username

Setting the UID to 1000 ensures we will not run in permissions issues when mapping volumes from our computer to the running container, once 1000 is the first UID assigned to a non root user in Linux, at least in Debian and Ubuntu ;)

Testing on Alpine 3.7

$ sudo docker run --rm -it alpine:3.7 sh                                                       
Unable to find image 'alpine:3.7' locally
3.7: Pulling from library/alpine
Digest: sha256:7b848083f93822dd21b0a2f14a110bd99f6efb4b838d499df6d04a49d0debf8b
Status: Downloaded newer image for alpine:3.7
/ # 
/# addgroup -g 1000 -S username  && adduser -u 1000 -S username -G username
/#
/ # grep username /etc/passwd
username:x:1000:1000:Linux User,,,:/home/username:/bin/false
/ # 
/ # grep username /etc/group
username:x:1000:username
/ # 

@Exadra37

Setting the UID to 1000 ensures we will not run in permissions issues when mapping volumes from our computer to the running container, once 1000 is the first UID assigned to a non root user in Linux, at least in Debian and Ubuntu ;)

docker-compose up  
Building orchestrator
Step 1/13 : FROM node:10-alpine
 ---> 7ca2f9cb5536
Step 2/13 : ENV user orchestrator
 ---> Using cache
 ---> bb0d334f29c6
Step 3/13 : ENV workdir /usr/src/app/
 ---> Using cache
 ---> a14d0251cf82
Step 4/13 : RUN addgroup -g 1000 -S $user && adduser -u 1000 -S -G $user $user
 ---> Running in 8545a3ed4d32
addgroup: gid '1000' in use
ERROR: Service 'orchestrator' failed to build: The command '/bin/sh -c addgroup -g 1000 -S $user && adduser -u 1000 -S -G $user $user' returned a non-zero code: 1

Here's what I do using docker-compose:

Dockerfile

FROM node:carbon-alpine
LABEL author="el que m'est"

ARG UID
ARG GID

USER root
RUN apk add --no-cache shadow sudo && \
    if [ -z "`getent group $GID`" ]; then \
      addgroup -S -g $GID cetacean; \
    else \
      groupmod -n cetacean `getent group $GID | cut -d: -f1`; \
    fi && \
    if [ -z "`getent passwd $UID`" ]; then \
      adduser -S -u $UID -G cetacean -s /bin/sh mobydick; \
    else \
      usermod -l mobydick -g $GID -d /home/mobydick -m `getent passwd $UID | cut -d: -f1`; \
    fi && \
    echo "mobydick ALL=(root) NOPASSWD:ALL" > /etc/sudoers.d/mobydick && \
    chmod 0440 /etc/sudoers.d/mobydick

WORKDIR /home/mobydick/app
RUN chown mobydick:cetacean /home/mobydick/app
USER mobydick

docker-compose.yml

version: '3'
services:
  app:
    build:
      context: .
      args:
        UID: ${UID}
        GID: ${GID}
    command: sh
    tty: true
    stdin_open: true
    volumes:
      - .:/home/mobydick/app
    ports:
      - ${PORT}:3000

.env

PORT=4000

package.json

{
  "scripts": {
    "dc": "GID=$(id -g) UID=$(id -u) docker-compose",
    "dc-build": "yarn dc build"
  }
}

Finally I run yarn dc-build to build a docker image.

@binarymist

addgroup: gid '1000' in use

As the error points out the group id 1000 is already in use.

This happens because the node image already as the user node with id 1000 and group id 1000.

╭─exadra37@exadra37-Vostro-470 ~  
β•°β”€βž€  sudo docker run --rm --user 1000 -it node:10-alpine sh
/ $ id
uid=1000(node) gid=1000(node) groups=1000(node)
/ $ cat /etc/passwd | grep -irn 1000 -
29:node:x:1000:1000:Linux User,,,:/home/node:/bin/sh
/ $ cat /etc/group | grep -irn 1000 -
49:node:x:1000:node

So no need for you to add your user when extending a node image, unless id is not 1000.

The @elquimista solution works with any user and group id and you can use it without docker compose, like:

sudo docker run --rm --user $(id -u) --env UID=$(id -u) --env GID=$(id -g) -it node:10-alpine sh

@Exadra37

As the error points out the group id 1000 is already in use.

Yeah, that's my point.

@elquimista
Your solution is looking good (terse, readable, not too clever). The best I could find until this was the suggested (https://denibertovic.com/posts/handling-permissions-with-docker-volumes/) which has a few problems of it's own (ncopa/su-exec#2 (comment))
Nice!

Just a couple of questions:

Why is mobydick added to the sudoers?
Why is mobydick able to run ALL commands as root? In your example there don't appear to be any commands following the sudoers insertion that require root perms.

@binarymist

  1. Adding mobydick to the sudoers list is generally not needed for images that are distributed to end consumers (i.e. they just run container from image and that's it). However, if you are a developer doing lots of development stuff on your local machine, you will find it convenient that mobydick is one of sudoers, when it comes to installing extra packages you might need for development, e.g., installing certain npm packages/ruby gems might require additional software packages installed.
    You can do this inside Dockerfile and rebuild the image, but that takes longer than doing it on the fly inside the current running container. (I am not saying doing it inside container is enough - you will still need to update Dockerfile accordingly about adding new packages.)

  2. In the Dockerfile I demonstrated above, all commands are run in the capacity of root user until it meets the line USER mobydick. All commands that come after this line will be assumed to run as mobydick.

Yip, understand that, good work!