Non-root user (Rootless)
Run containers as a non-root user: UID and GID, with the Next.js and Hono Dockerfiles.
- series
- Docker Workshop
- episode
- ตอนที่ 16 จาก 17
- read
- อ่าน 4 นาที
Problem
ใน Linux root-user จะทำงานได้ทุกอย่างเพราะมีสิทธิ์เป็นเหมือน admin เลย ใน Docker ก็เหมือนกันเพราะทำ Linux อีกที ถ้าเกิดว่า Container เราโดนเจาะ แล้ว container นั้นใช้ root user ในการรัน command ต่างๆ ก็เท่ากับว่าคนที่เจาะเข้ามาได้ ได้สิทธิ์ admin ไปเลยในทันที และ Docker deamon มันทำงานด้วยสิทธิ์ root อยู่แล้วก็มีโอกาสเสี่ยงที่คนเจาะจะออกมาที่ docker daemon ได้แล้วควบคุมเครื่อง Host ได้อีกที
ฉนั้นเราจะป้องกันไว้ก่อนด้วยการเปลี่ยนไปใช้ non-root user แทน การใช้ non-root user จะทำให้สิทธิ์ในการใช้งาน Container ลดลง เราจะให้ใช้สิทธิ์เท่าที่จำเป็น เป็นการจำกัดสิทธิ์ในการใช้งานไว้
UID & GID
user ใน Linux จะประกอบไปด้วย 2 ส่วน
- UID = User ID
- GID = Group ID
สร้าง group ด้วยคำสั่ง
addgroup --system --gid 1001 nodejsสร้าง user ด้วยคำสั่ง
adduser --system --uid 1001 nonroot`NextJS Dockerfile
FROM node:18-alpine AS base
# Install dependencies only when neededFROM base AS deps# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.RUN apk add --no-cache libc6-compatWORKDIR /app
# Install dependencies based on the preferred package managerCOPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./RUN \ if [ -f yarn.lock ]; then yarn --frozen-lockfile; \ elif [ -f package-lock.json ]; then npm ci; \ elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \ else echo "Lockfile not found." && exit 1; \ fi
# Rebuild the source code only when neededFROM base AS builderWORKDIR /appCOPY --from=deps /app/node_modules ./node_modulesCOPY . .
# Next.js collects completely anonymous telemetry data about general usage.# Learn more here: https://nextjs.org/telemetry# Uncomment the following line in case you want to disable telemetry during the build.# ENV NEXT_TELEMETRY_DISABLED 1
RUN \ if [ -f yarn.lock ]; then yarn run build; \ elif [ -f package-lock.json ]; then npm run build; \ elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \ else echo "Lockfile not found." && exit 1; \ fi
# Production image, copy all the files and run nextFROM base AS runnerWORKDIR /app
ENV NODE_ENV production# Uncomment the following line in case you want to disable telemetry during runtime.# ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejsRUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
# Set the correct permission for prerender cacheRUN mkdir .nextRUN chown nextjs:nodejs .next
# Automatically leverage output traces to reduce image size# https://nextjs.org/docs/advanced-features/output-file-tracingCOPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
# server.js is created by next build from the standalone output# https://nextjs.org/docs/pages/api-reference/next-config-js/outputCMD HOSTNAME="0.0.0.0" node server.jsจะเห็นว่า Dockerfile ที่ Next เตรียมมาให้แล้วมันใส่ User + Group มาให้ครบแล้ว ซึ่งเป็นตัวอย่างที่ดีมากๆ ถ้าใครต้องเขียน Dockerfile เอง เอา Dockerfile ของ Next เป็นตัวอย่างได้เลย ดีมากๆ
Let’s update backend’s Dockerfile
# syntax=docker/dockerfile:1ARG NODE_VERSION=22.2.0ARG PNPM_VERSION=8.15.5
# BuildFROM node:${NODE_VERSION}-alpine AS build
# Install pnpm.RUN --mount=type=cache,target=/root/.npm \ npm install -g pnpm@${PNPM_VERSION}
WORKDIR /app
RUN --mount=type=bind,source=package.json,target=package.json \ --mount=type=bind,source=pnpm-lock.yaml,target=pnpm-lock.yaml \ --mount=type=cache,target=/root/.local/share/pnpm/store \ pnpm install --frozen-lockfile
COPY . .
RUN pnpm run build && pnpm prune --prod
# FinalFROM node:${NODE_VERSION}-alpine AS runner
ENV NODE_ENV=productionWORKDIR /app
# add nonroot user and groupRUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 nonroot
# make uploads folderRUN mkdir -p ./uploads && chmod 700 ./uploads && chown -R nonroot:nodejs ./uploads
# when copy use flag --chown=<user>:<group>COPY --from=build --chown=nonroot:nodejs /app/dist ./distCOPY --from=build --chown=nonroot:nodejs /app/node_modules ./node_modulesCOPY --from=build --chown=nonroot:nodejs /app/package.json ./package.json
# finally use nonroot userUSER nonroot
EXPOSE 3333
CMD ["node", "dist/index.js"]