Skip to main content
Redhat Developers  Logo
  • Products

    Featured

    • Red Hat Enterprise Linux
      Red Hat Enterprise Linux Icon
    • Red Hat OpenShift AI
      Red Hat OpenShift AI
    • Red Hat Enterprise Linux AI
      Linux icon inside of a brain
    • Image mode for Red Hat Enterprise Linux
      RHEL image mode
    • Red Hat OpenShift
      Openshift icon
    • Red Hat Ansible Automation Platform
      Ansible icon
    • Red Hat Developer Hub
      Developer Hub
    • View All Red Hat Products
    • Linux

      • Red Hat Enterprise Linux
      • Image mode for Red Hat Enterprise Linux
      • Red Hat Universal Base Images (UBI)
    • Java runtimes & frameworks

      • JBoss Enterprise Application Platform
      • Red Hat build of OpenJDK
    • Kubernetes

      • Red Hat OpenShift
      • Microsoft Azure Red Hat OpenShift
      • Red Hat OpenShift Virtualization
      • Red Hat OpenShift Lightspeed
    • Integration & App Connectivity

      • Red Hat Build of Apache Camel
      • Red Hat Service Interconnect
      • Red Hat Connectivity Link
    • AI/ML

      • Red Hat OpenShift AI
      • Red Hat Enterprise Linux AI
    • Automation

      • Red Hat Ansible Automation Platform
      • Red Hat Ansible Lightspeed
    • Developer tools

      • Red Hat Trusted Software Supply Chain
      • Podman Desktop
      • Red Hat OpenShift Dev Spaces
    • Developer Sandbox

      Developer Sandbox
      Try Red Hat products and technologies without setup or configuration fees for 30 days with this shared Openshift and Kubernetes cluster.
    • Try at no cost
  • Technologies

    Featured

    • AI/ML
      AI/ML Icon
    • Linux
      Linux Icon
    • Kubernetes
      Cloud icon
    • Automation
      Automation Icon showing arrows moving in a circle around a gear
    • View All Technologies
    • Programming Languages & Frameworks

      • Java
      • Python
      • JavaScript
    • System Design & Architecture

      • Red Hat architecture and design patterns
      • Microservices
      • Event-Driven Architecture
      • Databases
    • Developer Productivity

      • Developer productivity
      • Developer Tools
      • GitOps
    • Secure Development & Architectures

      • Security
      • Secure coding
    • Platform Engineering

      • DevOps
      • DevSecOps
      • Ansible automation for applications and services
    • Automated Data Processing

      • AI/ML
      • Data Science
      • Apache Kafka on Kubernetes
      • View All Technologies
    • Start exploring in the Developer Sandbox for free

      sandbox graphic
      Try Red Hat's products and technologies without setup or configuration.
    • Try at no cost
  • Learn

    Featured

    • Kubernetes & Cloud Native
      Openshift icon
    • Linux
      Rhel icon
    • Automation
      Ansible cloud icon
    • Java
      Java icon
    • AI/ML
      AI/ML Icon
    • View All Learning Resources

    E-Books

    • GitOps Cookbook
    • Podman in Action
    • Kubernetes Operators
    • The Path to GitOps
    • View All E-books

    Cheat Sheets

    • Linux Commands
    • Bash Commands
    • Git
    • systemd Commands
    • View All Cheat Sheets

    Documentation

    • API Catalog
    • Product Documentation
    • Legacy Documentation
    • Red Hat Learning

      Learning image
      Boost your technical skills to expert-level with the help of interactive lessons offered by various Red Hat Learning programs.
    • Explore Red Hat Learning
  • Developer Sandbox

    Developer Sandbox

    • Access Red Hat’s products and technologies without setup or configuration, and start developing quicker than ever before with our new, no-cost sandbox environments.
    • Explore Developer Sandbox

    Featured Developer Sandbox activities

    • Get started with your Developer Sandbox
    • OpenShift virtualization and application modernization using the Developer Sandbox
    • Explore all Developer Sandbox activities

    Ready to start developing apps?

    • Try at no cost
  • Blog
  • Events
  • Videos

Building rootless containers for JavaScript front ends

March 4, 2021
Joel Lord
Related topics:
ContainersNode.jsKubernetes

Share:

    By default, most containers are run as the root user. It is much easier to install dependencies, edit files, and run processes on restricted ports when they run as root. As is usually the case in computer science, though, simplicity comes at a cost. In this case, containers run as root are more vulnerable to malicious code and attacks. To avoid those potential security gaps, Red Hat OpenShift won't let you run containers as a root user. This restriction adds a layer of security and isolates the containers.

    This article shows you how to run a JavaScript front-end application in a rootless container. The example builds on the code from my previous article, Making environment variables accessible in front-end containers.

    Building a rootless container

    Here is the Dockerfile we'll use for our example. As demonstrated in my previous article, you can use this Dockerfile to access environment variables from your Angular, React, or Vue.js applications:

    FROM node:14
    
    ENV JQ_VERSION=1.6
    RUN wget --no-check-certificate https://github.com/stedolan/jq/releases/download/jq-${JQ_VERSION}/jq-linux64 -O /tmp/jq-linux64
    RUN cp /tmp/jq-linux64 /usr/bin/jq
    RUN chmod +x /usr/bin/jq
    
    WORKDIR /app
    COPY . .
    RUN jq 'to_entries | map_values({ (.key) : ("$" + .key) }) | reduce .[] as $item ({}; . + $item)' ./src/config.json | ./src/config.tmp.json && mv ./src/config.tmp.json config.json
    RUN npm install && npm run build
    
    FROM nginx:1.17
    # Angular: ENV JSFOLDER=/usr/share/nginx/html/*.js
    # React: ENV JSFOLDER=/usr/share/nginx/html/static/js/*.js
    # VueJS: ENV JSFOLDER=/usr/share/nginx/html/js/*.js
    COPY ./start-nginx.sh /usr/bin/start-nginx.sh
    RUN chmod +x /usr/bin/start-nginx.sh
    WORKDIR /usr/share/nginx/html
    # Angular: COPY --from=0 /app/dist/ .
    # React: COPY --from=0 /app/build .
    # VueJS: COPY --from=0 /app/dist .
    ENTRYPOINT [ "start-nginx.sh" ]
    

    This container uses two stages to build the final container. In the first stage, it uses the node:14 image, which is running as root. The build process will eventually discard this container, so you don't need to worry about it.

    The second-stage container is the one that needs to be secured. The nginx base image is currently running as root, mainly so that it can run on port 80, which requires privileged access to enable. Once this container is ready to run rootless, it will run on port 8080. You will need to change the default nginx configuration for the container to run rootless. You will also need to make sure that the server itself is running as an unprivileged user. Finally, the user will need access to several files and folders.

    Let's get started with making this container a rootless one.

    Create the NGINX configuration file

    The first step is to create a new configuration file for NGINX. You can start with the most basic configuration file needed to run NGINX and build it from there:

    worker_processes auto;
    events {
      worker_connections 1024;
    }
    http {
      include /etc/nginx/mime.types;
      server {
        server_name _;
        index index.html;
        location / {
          try_files $uri /index.html;
          }
        }
    }

    Next, you need to change the server settings to run on port 8080 instead of the default port 80. You'll also need to change the default path that NGINX uses to serve files:

    http {
      ...
      server {
        listen 8080;
        ...
        location / {
          root /code;
          ...
        }
      }
    }

    The final nginx.conf file should look like this:

    worker_processes auto;
    events {
      worker_connections 1024;
    }
    http {
      include /etc/nginx/mime.types;
      server {
        listen 8080;
        server_name _;
        index index.html;
        location / {
          root /opt/app;
          try_files $uri /index.html;
        }
      }
    }

    Edit the Dockerfile

    Now that you have a new NGINX configuration file that lets the server run as a regular user, it's time to edit the Dockerfile. This modified container will run as user nginx. In this case, the NGINX base images provide the non-root user.

    In the second step of your build, right after you've specified your base image with the FROM statement, you can copy your new NGINX configuration file to overwrite the default one. Then, create an /opt/app folder and change its ownership:

    FROM nginx:1.17
    COPY ./nginx.conf /etc/nginx/nginx.conf
    RUN mkdir -p /opt/app && chown -R nginx:nginx /opt/app && chmod -R 775 /opt/app
    

    Don't forget to change the JSFOLDER variable. This will ensure that your environment variables are still injected by the bash script.

    # Angular
    # ENV JSFOLDER=/opt/app/*.js
    # React
    # ENV JSFOLDER=/opt/app/static/js/*.js
    # VueJS
    # ENV JSFOLDER=/opt/app/js/*.js
    

    Change the file ownership

    Next, you need to give NGINX access to run a series of files and folders for caching and logging purposes. You can change the ownership of all of them in a single RUN statement, using ampersands to chain the commands:

    RUN chown -R nginx:nginx /var/cache/nginx && \
       chown -R nginx:nginx /var/log/nginx && \
       chown -R nginx:nginx /etc/nginx/conf.d
    

    NGINX also requires an nginx.pid file. This file does not exist yet, so you need to create it and assign ownership to the nginx user:

    RUN touch /var/run/nginx.pid && \
       chown -R nginx:nginx /var/run/nginx.pid
    

    Update the group and permissions

    Finally, you will change the group for those files and folders and change the permissions so that NGINX can read and write the folders:

    RUN chgrp -R root /var/cache/nginx /var/run /var/log/nginx /var/run/nginx.pid && \
       chmod -R 775 /var/cache/nginx /var/run /var/log/nginx /var/run/nginx.pid
    

    Switch to the rootless user

    Now that you've adjusted all the permissions, you can tell Docker to switch over to the nginx user using the USER statement. You can then copy the files from the builder step into the /opt/app folder using the --chown flag, which makes the files accessible by the nginx user. Finally, you will tell Docker that this new image uses a different port. Use the EXPOSE statement for port 8080:

    USER nginx
    WORKDIR /opt/app
    COPY --from=builder --chown=nginx  .
    RUN chmod -R a+rw /opt/app
    EXPOSE 8080
    

    The final front-end Dockerfile will look like this:

    FROM node:14
    
    ENV JQ_VERSION=1.6
    RUN wget --no-check-certificate https://github.com/stedolan/jq/releases/download/jq-${JQ_VERSION}/jq-linux64 -O /tmp/jq-linux64
    RUN cp /tmp/jq-linux64 /usr/bin/jq
    RUN chmod +x /usr/bin/jq
    
    WORKDIR /app
    COPY . .
    RUN jq 'to_entries | map_values({ (.key) : ("$" + .key) }) | reduce .[] as $item ({}; . + $item)' ./src/config.json | ./src/config.tmp.json && mv ./src/config.tmp.json config.json
    RUN npm install && npm run build
    
    FROM nginx:1.17
    # Angular
    # ENV JSFOLDER=/opt/app/*.js
    # React
    # ENV JSFOLDER=/opt/app/static/js/*.js
    # VueJS
    # ENV JSFOLDER=/opt/app/js/*.js
    COPY ./nginx.conf /etc/nginx/nginx.conf
    RUN mkdir -p /opt/app && chown -R nginx:nginx /opt/app && chmod -R 775 /opt/app
    RUN chown -R nginx:nginx /var/cache/nginx && \
       chown -R nginx:nginx /var/log/nginx && \
       chown -R nginx:nginx /etc/nginx/conf.d
    RUN touch /var/run/nginx.pid && \
       chown -R nginx:nginx /var/run/nginx.pid
    RUN chgrp -R root /var/cache/nginx /var/run /var/log/nginx /var/run/nginx.pid && \
       chmod -R 775 /var/cache/nginx /var/run /var/log/nginx /var/run/nginx.pid
    COPY ./start-nginx.sh /usr/bin/start-nginx.sh
    RUN chmod +x /usr/bin/start-nginx.sh
    
    EXPOSE 8080
    WORKDIR /opt/app
    # Angular
    # COPY --from=0 --chown=nginx /app/dist/ .
    # React
    # COPY --from=0 /app/build .
    # VueJS
    # COPY --from=0 /app/dist .
    RUN chmod -R a+rw /opt/app
    USER nginx
    ENTRYPOINT [ "start-nginx.sh" ]

    Your new Dockerfile is ready to go! You can test it out by using a docker build followed by a docker run. Don't forget to map the new port since this container doesn't run on port 80 anymore:

    docker build -t frontend .
    docker run -d -p 8080:8080 --rm --name front -e ENV=prod -e BASE_URL=/api frontend
    

    Conclusion

    You now have everything needed to run your JavaScript front end in a secure container. You can reuse the image we developed in this article for all of your JavaScript projects, whether you are using Angular, React, or Vue.js. The front end not only runs securely but also lets you inject environment variables into your code. You can find all the examples and source code from this article on GitHub.

    Recent Posts

    • More Essential AI tutorials for Node.js Developers

    • How to run a fraud detection AI model on RHEL CVMs

    • How we use software provenance at Red Hat

    • Alternatives to creating bootc images from scratch

    • How to update OpenStack Services on OpenShift

    Red Hat Developers logo LinkedIn YouTube Twitter Facebook

    Products

    • Red Hat Enterprise Linux
    • Red Hat OpenShift
    • Red Hat Ansible Automation Platform

    Build

    • Developer Sandbox
    • Developer Tools
    • Interactive Tutorials
    • API Catalog

    Quicklinks

    • Learning Resources
    • E-books
    • Cheat Sheets
    • Blog
    • Events
    • Newsletter

    Communicate

    • About us
    • Contact sales
    • Find a partner
    • Report a website issue
    • Site Status Dashboard
    • Report a security problem

    RED HAT DEVELOPER

    Build here. Go anywhere.

    We serve the builders. The problem solvers who create careers with code.

    Join us if you’re a developer, software engineer, web designer, front-end designer, UX designer, computer scientist, architect, tester, product manager, project manager or team lead.

    Sign me up

    Red Hat legal and privacy links

    • About Red Hat
    • Jobs
    • Events
    • Locations
    • Contact Red Hat
    • Red Hat Blog
    • Inclusion at Red Hat
    • Cool Stuff Store
    • Red Hat Summit

    Red Hat legal and privacy links

    • Privacy statement
    • Terms of use
    • All policies and guidelines
    • Digital accessibility

    Report a website issue