Deploy a Rails Application on Kubernetes with a Custom Helm Chart

Introduction

The Ruby on Rails framework is a popular choice with developers who want to create Web applications with ease. It is based on the Ruby language and it allows you to write less code with its predefined layout and additional libraries for simplifying the programming of web applications.

Rails is included in Bitnami's catalog of development containers. Bitnami's Rails development container includes a full Rails environment and an SQL database (MariaDB by default) for creating web applications, so developers only need to focus on coding.

Kubernetes is the best way to run application deployments on clusters of hosts. This solution allows you to automate the deployment, the scaling and management of application containers. To more easily deploy and manage the application containers in a Kubernetes cluster, you can use Helm charts. In the same way that a Dockerfile contains instructions on how to build images in running containers, Helm charts define how the application will be deployed in Kubernetes. They specify object definitions, services, dependencies, number of pods, and so on.

This guide walks you through the process of running an example Rails application on a Kubernetes cluster. It uses a simple "Hello World!" Rails application based on the latest Bitnami Rails container image. It walks you through the process of creating a Dockerfile and using it as a starting point for a custom Helm chart that automates application deployment in a Kubernetes cluster. Once the application is deployed and working, this guide also explores how to modify the source code for publishing a new application release and how to perform rolling updates in Kubernetes using the Helm CLI.

Assumptions and prerequisites

This guide will show you how to deploy an example Rails application in a Kubernetes cluster. The example application is a typical "Hello World!" application.

This guide makes the following assumptions:

Step 1: Create the example application

To get started, you need to have a Rails application. If you don't have one, an easy way to do this is to create a Rails boilerplate project which provides all the resources for a simple web application. Follow the steps below:

  • Run a Docker container using the latest Bitnami Rails image by executing the docker run command, as shown below:

    docker run --rm -v $(pwd):/app bitnami/rails:latest rails new my_app --database mysql
    

    This command generates a new container (that will be removed after the container is stopped thanks to the --rm option) using the Bitnami Rails Docker image. It mounts the current directory (retrieved by $(pwd)) as a volume inside the /app directory in the container. Then, it generates a new Rails application using the rails command.

    At the end of the process and once the container terminates, the Rails application source code will be within the my_app subdirectory of the current working directory.

  • Edit the my_app/config/database.yml file to specify the database configuration that will be used during the Helm chart creation. Change the default values as follows:

    default: &default
        adapter: mysql2
        encoding: utf8mb4
        pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
        username: root
        password: <%= ENV['MARIADB_PASSWORD'] %>
        host: <%= ENV['DATABASE_HOST']%>
    

Step 2: Create a Dockerfile

A Dockerfile is a text file that contains instructions on how to build a Docker image. Each line indicates a command or instruction for assembling all the pieces that comprise the image.

In the same directory where you ran the container, create a file named Dockerfile with the following content:

# This Dockerfile uses the latest version of the Bitnami Rails Docker image
FROM bitnami/rails:latest

# Copy app's source code to the /app directory
COPY my_app /app

# The following operations need to be executed as root
USER root

# Install the Javascript environment needed by rails
RUN install_packages nodejs

# Actions will be performed by the user 'bitnami', so it's good practice
# to explicitly set the required permissions
RUN chown -R bitnami:bitnami /app /home/bitnami

# Change the effective UID from 'root' to 'bitnami'
# Never run application code as 'root'!
USER bitnami

# The application's directory will be the working directory
WORKDIR /app

# Install the application dependencies defined in the Gemfile
RUN bundle install

Step 3: Build the Docker image

Once you've created the Dockerfile, you can build the Docker image. Follow the instructions below to build the Docker image:

  • Run the docker build command in the same directory where the Dockerfile is. Add a tag to identify the current version of the application, such as 0.1.0. Remember to replace the USERNAME placeholder with your Docker Hub username.

    docker build -t USERNAME/my-rails-app:0.1.0 .
    
  • Check that the image has been created in your local repository by executing the command below:

    docker images | grep my-rails-app
    

Step 4: Publish the Docker image

Once the Docker image is built with your application code, upload it to a public registry. This guide uses Docker Hub, but you can select others, such as:

To upload the image to Docker Hub, follow the steps below:

  • Log in to Docker Hub:

    docker login
    
  • Push the image to your Docker Hub account. Replace the USERNAME placeholder with your Docker username:

    docker push USERNAME/my-rails-app:0.1.0
    

Confirm that you see the image in your Docker Hub repositories dashboard before proceeding to the next step.

Step 5: Create the Helm chart

To get started with a new Helm chart, use the helm create command. This command creates a scaffold with sample files that can be modified to build a custom chart. The following instructions will take you through the process of creating a new chart and customizing it to include your Rails sample application.

  • In your working directory, execute the helm create command to create a new chart named my-rails-app:

    helm create my-rails-app
    
  • Change to the my-rails-app directory:

    cd my-rails-app
    
  • Helm will have created a directory with the file structure shown below:

    my-rails-app
    |-- Chart.yaml
    |-- charts
    |-- templates
    |   |-- NOTES.txt
    |   |-- _helpers.tpl
    |   |-- deployment.yaml
    |   |-- ingress.yaml
    |   `-- service.yaml
    `-- values.yaml
    
  • Edit the values.yaml file and replace the existing content with the following. Note that this configures the deployment to use a LoadBalancer, so that the Rails application can be accessed from outside the cluster. Remember to replace the USERNAME placeholder with your Docker Hub username.

    # Default values for my-rails-app.
    # This is a YAML-formatted file.
    # Declare variables to be passed into your templates.
    
    replicaCount: 1
    
    image:
      repository: USERNAME/my-rails-app
      tag: 0.1.0
      pullPolicy: IfNotPresent
    
    imagePullSecrets: []
    nameOverride: ""
    fullnameOverride: ""
    
    serviceAccount:
      # Specifies whether a service account should be created
      create: true
      # Annotations to add to the service account
      annotations: {}
      # The name of the service account to use.
      # If not set and create is true, a name is generated using the fullname template
      name: ""
    
    podAnnotations: {}
    
    podSecurityContext: {}
      # fsGroup: 2000
    
    securityContext: {}
      # capabilities:
      #   drop:
      #   - ALL
      # readOnlyRootFilesystem: true
      # runAsNonRoot: true
      # runAsUser: 1000
    
    service:
      type: LoadBalancer
      port: 80
    
    ingress:
      enabled: false
      annotations: {}
        # kubernetes.io/ingress.class: nginx
        # kubernetes.io/tls-acme: "true"
      hosts:
        - host: chart-example.local
          paths: []
      tls: []
      #  - secretName: chart-example-tls
      #    hosts:
      #      - chart-example.local
    
    resources: {}
      # We usually recommend not to specify default resources and to leave this as a conscious
      # choice for the user. This also increases chances charts run on environments with little
      # resources, such as Minikube. If you do want to specify resources, uncomment the following
      # lines, adjust them as necessary, and remove the curly braces after 'resources:'.
      # limits:
      #   cpu: 100m
      #   memory: 128Mi
      # requests:
      #   cpu: 100m
      #   memory: 128Mi
    
    autoscaling:
      enabled: false
      minReplicas: 1
      maxReplicas: 100
      targetCPUUtilizationPercentage: 80
      # targetMemoryUtilizationPercentage: 80
    
    nodeSelector: {}
    
    tolerations: []
    
    affinity: {}
    
  • Edit the templates/deployment.yaml file and fill it with the following deployment configuration:

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: {{ include "my-rails-app.fullname" . }}
      labels:
        {{- include "my-rails-app.labels" . | nindent 4 }}
    spec:
    {{- if not .Values.autoscaling.enabled }}
      replicas: {{ .Values.replicaCount }}
    {{- end }}
      selector:
        matchLabels:
          {{- include "my-rails-app.selectorLabels" . | nindent 6 }}
      template:
        metadata:
        {{- with .Values.podAnnotations }}
          annotations:
            {{- toYaml . | nindent 8 }}
        {{- end }}
          labels:
            {{- include "my-rails-app.selectorLabels" . | nindent 8 }}
        spec:
          {{- with .Values.imagePullSecrets }}
          imagePullSecrets:
            {{- toYaml . | nindent 8 }}
          {{- end }}
          serviceAccountName: {{ include "my-rails-app.serviceAccountName" . }}
          securityContext:
            {{- toYaml .Values.podSecurityContext | nindent 8 }}
          containers:
            - name: {{ .Chart.Name }}
              securityContext:
                {{- toYaml .Values.securityContext | nindent 12 }}
              image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
              imagePullPolicy: {{ .Values.image.pullPolicy }}
              ports:
                - name: http
                  containerPort: 3000
                  protocol: TCP
              env:
              - name: DATABASE_HOST
                value: {{ template "mariadb.fullname" . }}
              - name: MARIADB_PASSWORD
                valueFrom:
                  secretKeyRef:
                    name: {{ template "mariadb.fullname" . }}
                    key: mariadb-root-password
              livenessProbe:
                httpGet:
                  path: /
                  port: http
              readinessProbe:
                httpGet:
                  path: /
                  port: http
              resources:
                {{- toYaml .Values.resources | nindent 12 }}
          {{- with .Values.nodeSelector }}
          nodeSelector:
            {{- toYaml . | nindent 8 }}
          {{- end }}
          {{- with .Values.affinity }}
          affinity:
            {{- toYaml . | nindent 8 }}
          {{- end }}
          {{- with .Values.tolerations }}
          tolerations:
            {{- toYaml . | nindent 8 }}
          {{- end }}
    
    TipThis guide is for development environments. If working in a production environment, set the Rails environment variables as follows. These changes apply to all the environment variables present in the above file.
    - name: RAILS_ENV
      value: "production"
    
  • Edit the templates/_helpers.tpl file and append these lines to the end of the file:

    {{- define "mariadb.fullname" -}}
    {{- printf "%s-%s" .Release.Name "mariadb" | trunc  63 | trimSuffix "-" -}}
    {{- end -}}
    
  • Edit the Chart.yaml file and append the following lines to it:

    dependencies:
    - name: mariadb
      version: 8.x.x
      repository: https://charts.bitnami.com/bitnami
      condition: mariadb.enabled
    
  • Install missing dependencies with helm dep. The Helm chart used in this guide includes MariaDB dependencies specified in the recently-created requirements.yaml file. Execute the commands below to finish creating the chart:

    helm dep list
    helm dep update .
    
  • Test that the syntax of the files is correct by running the helm lint command:

    helm lint .
    

Step 6: Deploy the example application on Kubernetes

At this point, you have built a Docker image, published it in a container registry and created your custom Helm chart. It's now time to deploy the example Rails application within a Kubernetes cluster.

Tip

Before performing the following steps, make sure you have a Kubernetes cluster running with Helm installed. For detailed instructions, refer to our starter tutorials for different platforms.

To deploy the example application using the current Helm chart, follow these steps:

  • First, make sure that you are able to connect to your Kubernetes cluster by executing the command below:

    kubectl cluster-info
    
  • Deploy the Helm chart with the helm install command. This will create two pods within the cluster, one for the Rails service and the other for the MariaDB service. DB-PASSWORD is a placeholder, set it to the password you wish to use for the database root user account.

    helm install --set mariadb.auth.rootPassword=DB-PASSWORD my-rails-app .
    
  • Once the chart has been installed, you will see a lot of useful information about the deployment. The application won't be available until database configuration is complete. If you inspect the logs of the Rails pod, you should be able to see the database configuration in action:

Pod configuration

To get the application URL, run the commands shown in the "Notes" output of the deployment command, or use the kubectl get svc command to obtain the load balancer's public IP address. Enter the IP address in your browser to access the application. You should see the default welcome page shown below:

Rails welcome page

Congratulations! Your Rails application has been successfully deployed on Kubernetes.

Step 7: Update the source code

As a developer, you'll understand that your application may need new features or bug fixes in the future. To release a new Docker image, you only have to perform a few basic steps: change your application source code, rebuild and republish the image in your selected container registry. Follow these instructions to complete the source code update.

  • In the my_app/public directory, create a file named my_page.html. Add this welcome message to the file:

    Hello world
    

    Save the file.

  • Change to the directory where the Dockerfile is and rebuild the image by running the docker build command. Add a tag to identify the current version of the application, such as 0.1.1. Replace the USERNAME placeholder with your Docker Hub username.

    docker build -t USERNAME/my-rails-app:0.1.1  .
    
  • Check that the new image has been created in your local repository:

    docker images | grep my-rails-app
    
  • Log in to Docker Hub (if you are not already logged in):

    docker login
    
  • Push the image to your Docker Hub account. Replace the USERNAME placeholder with your Docker Hub username:

    docker push USERNAME/my-rails-app:0.1.1
    
  • Navigate to your Docker account. Select the USERNAME/my-rails-app repository to show the content. Confirm that the application release is seen in the tag list:

Docker image build process

Step 8: Roll out updates (and perform rollbacks)

Rolling updates allow you to run the latest image of your application in a Kubernetes cluster. Any change that you have made will be reflected in your charts by executing the helm upgrade command. Rollbacks also have the advantage of allowing developers to go back to the previous version of their application in any moment. As with updates, you can perform a rollback just by running the helm rollback command. Here's how to upgrade or rollback the deployment using the Helm CLI.

Update the Helm chart

To deploy a new application release in a Kubernetes cluster, follow the steps below:

  • Change to the my-rails-app directory, where you have the Helm chart files. Edit the values.yaml file and replace the tag field with a new tag reflecting the latest version of the application, such as 0.1.1 in this example.

  • Run the helm upgrade command followed by the name of the chart. Replace the DB-PASSWORD placeholder with the same password originally set for the database root account.

    helm upgrade --set mariadb.auth.rootPassword=DB-PASSWORD my-rails-app .
    
  • See what revisions have been made to the chart by executing the helm history command:

    helm history my-rails-app
    
Docker image build process
  • Enter the application URL from Step 6, followed by /my_page.html. You should see the welcome message for the new application release:
Docker image build process

Rollback the Helm chart

If you want to go back to a previous version of your Helm chart:

  • Bring up a list of the chart's revisions by executing the helm history command:

    helm history my-rails-app
    
Docker image build process
  • Execute the helm rollback command including the name of the chart and the revision number you want to go back to:

    helm rollback my-rails-app 1
    
  • Enter the application URL you were given in Step 6, and you should see the default Rails welcome page again:

Docker image build process

Useful links

To learn more about the topics discussed in this guide, use the links below: