A Neate Blog


9 March 2020

Running Oracle JET in Docker

Tags: Oracle JET - Docker - Dockerfile - Nginx - Kubernetes


In the previous Dockerfile articles, we looked at a Dockerfile example that was an Oracle JET application, in this post we dive deeper and enhance this Dockerfile which results in a great way to run an Oracle JET application via Docker, whether that’s on a local machine or in the cloud.

For this article Nginx will be used as the web server.

The first thing we can do is setup our nginx stage so that it can be cached whilst containing everything we need to start the image.

FROM nginx:alpine AS nginx-container

# Remove the default nginx config
RUN rm -v /etc/nginx/nginx.conf

# Create the folder structure to form the URI Path
RUN mkdir -p /usr/share/nginx/html/test/ui

# Update folder permissions, especially important if you're running inside RedHat OpenShit
RUN chmod -R a+rwx /usr/share

# Set working directory
WORKDIR /usr/share/nginx/html/test/ui

The above commands result in the same output meaning they are cached which means this entire stage should be placed at the start of the Dockerfile.

Our second step is to create our base build container to compile the application.

FROM node:alpine AS base-jet-build

# Create out JET Specific Folders
RUN mkdir -p /usr/src/ui/web \
  && mkdir -p /usr/src/ui/src \
  && mkdir -p /usr/src/ui/scripts

# Update permissions
RUN chmod -R a+rwx /usr/src/ui

# Set working directory
WORKDIR /usr/src/ui

# Instal OJET Cli and production dependencies
RUN npm install -g @oracle/ojet-cli@8.0.0 \
  && npm install @oracle/oraclejet@8.0.0 \
  && npm install @oracle/oraclejet-tooling@8.0.0

The final two lines of this stage install the Oracle JET and Oracle JET Tooling modules before the package.json or package-lock.json files are copied over, the sole reason for doing this is due to the package.json being version bumped during every CI release meaning it can;t be cacheable, therefore by explicitly installing only the production dependencies, we can avoid doing npm install as we have manually selected the specific modules.

Like the first nginx stage, this stage results in the same command output meaning everything so far can be retrieved from the Docker cache.

Our current Dockerfile looks like the following:

FROM nginx:alpine AS base-nginx-container

# Remove the default nginx config
RUN rm -v /etc/nginx/nginx.conf

# Create the folder structure to form the URI Path
RUN mkdir -p /usr/share/nginx/html/test/ui

# Update folder permissions, especially important if you're running inside RedHat OpenShit
RUN chmod -R a+rwx /usr/share

# Set working directory
WORKDIR /usr/share/nginx/html/test/ui

FROM node:alpine AS base-jet-build

# Create out JET Specific Folders
RUN mkdir -p /usr/src/ui/web \
  && mkdir -p /usr/src/ui/src \
  && mkdir -p /usr/src/ui/scripts

# Update permissions
RUN chmod -R a+rwx /usr/src/ui

# Set working directory
WORKDIR /usr/src/ui

# Instal OJET Cli globally
RUN npm install -g @oracle/ojet-cli@8.0.0 \
  && npm install @oracle/oraclejet@8.0.0 \
  && npm install @oracle/oraclejet-tooling@8.0.0

The next step in the process is to actually build/compile the Oracle JET application, as stated earlier we already have our dependencies installed in the second stage therefore we can use the second stage as our base image.

FROM base-jet-build AS build-container

# Copy Oracle JET scripts folder
COPY scripts /usr/src/ui/scripts/

# Copy Oracle JET config file
COPY oraclejetconfig.json /usr/src/ui/

# Copy source code
COPY src /usr/src/ui/src/

# Start the build process in release mode
RUN ojet build --release

The release flag on the ojet build command is important as it will minify the files and result in a finalised structure suitable for Production deployments. Unfortunately, this stage can’t be cached because chances are you’ve changed some source code! I’ve seen instances where people break down their source code into different folder based copying for example, COPY assets, COPY html, COPY js etc because not all source files and folders change but I think this is a case-by-case basis and shouldn’t be explicitly decided across all of your docker images. It’s for you to decide the performance benefit of breaking a single COPY command into multiple commands compared to how often multiple files/folders change…

Now that we have our compiled Oracle JET application, we can focus on starting the web server to actually run our final application

FROM base-nginx-container

# Expose specific port
EXPOSE 8080

# Copy my nginx Config
COPY nginx.conf /etc/nginx/

# Copy the Source Code
COPY --from=build-container /usr/src/ui/web /usr/share/nginx/html/test/ui

# Copy over the package.json - This is personal preference if you wish to have the json metadata in your final application
COPY package.json /usr/share/nginx/html/test/ui

# Start nginx service
CMD ["nginx","-g","daemon off;"]

And that’s it, we’re utilising multi-stage Docker builds to speed-up the build time and also reduce the final image size, before starting this process the final Oracle JET image size was over 300MB, whereas now it’s down to 47.3MB and after the first run, the entire image is built in less than 5 seconds due to the caching strategies implemented.

Final Dockerfile:

FROM nginx:alpine AS base-nginx-container

# Remove the default nginx config
RUN rm -v /etc/nginx/nginx.conf

# Create the folder structure to form the URI Path
RUN mkdir -p /usr/share/nginx/html/test/ui

# Update folder permissions, especially important if you're running inside RedHat OpenShit
RUN chmod -R a+rwx /usr/share

# Set working directory
WORKDIR /usr/share/nginx/html/test/ui

FROM node:alpine AS base-jet-build

# Create out JET Specific Folders
RUN mkdir -p /usr/src/ui/web \
  && mkdir -p /usr/src/ui/src \
  && mkdir -p /usr/src/ui/scripts

# Update permissions
RUN chmod -R a+rwx /usr/src/ui

# Set working directory
WORKDIR /usr/src/ui

# Instal OJET Cli globally
RUN npm install -g @oracle/ojet-cli@8.0.0 \
  && npm install @oracle/oraclejet@8.0.0 \
  && npm install @oracle/oraclejet-tooling@8.0.0

FROM base-jet-build AS build-container

# Copy Oracle JET scripts folder
COPY scripts /usr/src/ui/scripts/

# Copy Oracle JET config file
COPY oraclejetconfig.json /usr/src/ui/

# Copy source code
COPY src /usr/src/ui/src/

# Start the build process in release mode
RUN ojet build --release

FROM base-nginx-container

# Expose specific port
EXPOSE 8080

# Copy my nginx Config
COPY nginx.conf /etc/nginx/

# Copy the Source Code
COPY --from=build-container /usr/src/ui/web /usr/share/nginx/html/test/ui

# Copy over the package.json - This is personal preference if you wish to have the json metadata in your final application
COPY package.json /usr/share/nginx/html/test/ui

# Start nginx service
CMD ["nginx","-g","daemon off;"]

Basic Nginx Conf:

worker_processes 1;

events { worker_connections 1024; }

http {
    include    mime.types;
    sendfile on;
    server_tokens off;
    server {
        root /usr/share/nginx/html/;
        index index.html;
        server_name localhost;
        listen 8080;
    }
}

TL;DR:- Oracle JET can be served via a simple web server tool such as Nginx or Express, scroll down to the end to see the final Dockerfile.

Useful Links: