Module 7 Lesson 2: Running as Non-Root
Break the 'Root' habit. Learn why running containers as the administrator is the biggest risk in Docker and how to implement a secure USER instruction.
Module 7 Lesson 2: Running as Non-Root
By default, Docker containers run as the root user (ID 0). In Linux, root has the power to do anything. If an attacker finds a vulnerability in your code (e.g., a file upload bug) and they are running as root, they might be able to find a way to escape the container and take over your entire server.
1. The Risk: Escalation and Escape
If a process is root inside a container, it is "kind of" root on the host too.
- If you mount a sensitive folder (like
/etc/shadow) with a bind mount, the root container can add or delete users on your actual laptop/server. - Many security "Exploits" rely on having root access to load malicious drivers or change system settings.
2. The Solution: The USER Instruction
You should always create a "Unprivileged" user in your Dockerfile and switch to them before the CMD starts.
FROM python:3.9-slim
# 1. Create a non-root group and user
RUN groupadd -r appgroup && useradd -r -g appgroup appuser
# 2. Setup your app (still as root so you can install things)
WORKDIR /home/appuser
COPY . .
RUN chown -R appuser:appgroup /home/appuser
# 3. SWITCH to the user
USER appuser
# 4. Start the app (now running as appuser)
CMD ["python", "app.py"]
3. The "Permissions" Headache
Once you switch to a non-root user, you will face "Permission Denied" errors.
- Solution: You must
chown(change owner) the files your app needs to work with before you switch to the user. - Ports: Non-root users cannot listen on ports below 1024 (like 80 or 443).
- The Workaround: Have your app listen on port 8080 and use Docker's port mapping (
-p 80:8080) to map it to the outside.
- The Workaround: Have your app listen on port 8080 and use Docker's port mapping (
4. Overriding at Runtime
You can also force a specific user ID when you start a container using the --user flag.
docker run --user 1000 node:alpine id
(This is often used in CI/CD pipelines to match the UID of the build server).
Exercise: The Permission Test
- Run
docker run alpine whoami. What is the result? - Now, write a Dockerfile that:
- Starts from
alpine. - Adds a user named
security_intern. - Uses the
USERinstruction to switch to them. - Runs
whoamias the final command.
- Starts from
- Build and run it. What is the result?
- Try to run
apk add curlas that user usingdocker exec. Does it work? Why is this good for security?
Summary
Running as non-root is the #1 defense against "Container Escape" attacks. While it requires a bit more work in your Dockerfile to handle permissions, it is the standard for any professional or financial application.
Next Lesson: Automated defense: Image scanning and vulnerability management.