Deploying a React app using Min.io
Tags: react, kubernetes, cloud, selfhosted
Published: 2021-07-11 15:55:23

In previous posts I had written about how to set up a Kubernetes cluster on self-hosted hardware with the purpose of hosting applications on a Local Area Network (LAN) to use as an intranet of sorts. Today I'll cover how to deploy client-side applications onto Kubernetes cluster which was provisioned.

## Servers to Cloud Platforms

First, let's talk about how the deployments would be handled on a cloud platform. During the .com days, a server would have to be provisioned to host web applications (i.e. Apache/Nginx). The static website would then have to be copied into a static HTML folder (i.e. `/var/www/html`) in order to be served to clients via the web server.
The process of web hosting was improved with the rise of containerization. Instead of having to set up and manage a web server directly on your server hardware, now you can pull a pre-configured image and mount your static web content onto a container which would drastically improve website deployment times.
The rise of cloud platforms furthers the improvement by abstracting away the deployment environment from the developer entirely so that more focus can be placed on the website assets themselves instead of provisioning and configuring servers.
Cloud platforms implement hosted storage using a standard called Object-Based-Storage. Object-based-storage utilizes web endpoints to control and manage assets uploaded to a server. Site content can be managed and served directly using the HTTP protocol. [AWS S3](https://aws.amazon.com/s3/) is a perfect example of how object-based-storage works.

## Minio

[Minio](https://min.io) is a self-hosted service which provides object-based-storage using the AWS S3 protocol; meaning that the endpoints which Minio provides are interchangeable with S3. Minio can be used as a gateway to hosted object-based-storage services which reside on multiple cloud platforms (i.e. Google Cloud Platform (GCP), Amazon Web Services (AWS), Microsoft Azure), but for the sake of this deployment, Minio will be used as a frontend for a mounted volume on our Kubernetes cluster.

## Deploying the Services

Before we can deploy a static website to our Kubernetes cluster, we will first have to provision a Minio server.
The Minio documentation utilizes a [Minio Operator](https://docs.min.io/minio/k8s/deployment/deploy-minio-operator.html#deploy-operator-kubernetes) and a [kubectl krew](https://krew.sigs.k8s.io/docs/user-guide/setup/install/) plugin to provision servers. Utilizing these tools will be covered in a later production release document. For the sake of this tutorial, deploying Minio will be handled with the [Minio helm chart](https://github.com/bitnami/charts/tree/master/bitnami/minio)

Utilizing [Terraform](https://terraform.io), the Minio server can be deployed to Helm with the following snippet:

```tf
provider "helm" {}

resource "helm_release" "rel_minio" {
  name       = "files"
  chart      = "minio"
  repository = "https://charts.bitnami.com/bitnami"
}
```
The helm chart can be deployed with the following commands:

```bash
terraform plan
terraform apply
```

Once the helm deployment has been completed, the service will be available from the Kubernetes cluster. In order to interact with the services, the credentials will have to be read in from the Kubernetes secrets which are generated by the helm chart. The following commands retrieve the secrets from Kubernetes and store them in environment variables:

```bash
export minio_access_key=$(kubectl get secret files-minio --namespace econovizer -o=jsonpath='{.data.access-key}' | base64 --decode)
export minio_secret_key=$(kubectl get secret files-minio --namespace econovizer -o=jsonpath='{.data.secret-key}' | base64 --decode)
```

We then have to port-forward the Kubernetes service in order to access it.

```bash
kubectl port-forward services/files-minio 9000:9000
```

## Configuring the Minio Client

Minio provides a CLI called [mc](https://docs.min.io/docs/minio-client-quickstart-guide.html) which can be utilized to interact with the Minio server. We have to call the `mc` tool with the `$minio_secret_key` and the `$minio_access_key` environment variables we created earlier.

```bash
mc alias set local http://127.0.0.1:9000 $minio_access_key $minio_secret_key
```

With the client configured we can now create a bucket for hosting our static site.

```bash
mc mb local/static
```

Before assets can be served from the bucket, the bucket needs to be configured for public asses.

```bash
mc policy set download local/static
```

## Creating the React Application

With the hosting environment established, we can now create our static website. The easiest way to set up the static website is using [Create React App](https://create-react-app.dev/).

```bash
npx create-react-app my-app
```

This command will create a React application with the name `my-app` in the current folder. We need to change into the `my-app` folder -- `cd my-app`. Build the project with the command `npm run build`. The build command creates a new folder `build`.

With the build folder created, we can deploy the build to our bucket with the `mc` command

```bash
mc cp -r build/* local/static/
mc ls local/static # list the files which were just uploaded to the local/static bucket
```

Ideally from this point, you would be able to access the static site from `http://localhost:9000/static`, however Minio has a limitation which prevents it from serving up files unless they were referenced directly.
`http://localhost:9000/static` will return an XML document containing a `ListBucketResult` instead of `index.html`. `http://localhost:9000/static/index.html` will return the desired web page. Since the URL would end with `index.html`, React would be looking for a web root and fail to load.

Fortunately, the issue could  be fixed with a proxy application: [s3www](https://github.com/harshavardhana/s3www).

To simplify the deployment of s3www, I created a Terraform template which deploys the following resources to Kubernetes:
- s3www pod deployment
- s3www Kubernetes service
- Ingress which proxies the s3www service

{% gist https://gist.github.com/mikeyGlitz/ab8726ea7a039252450d21c87465014f %}

This file can be run with the following commands:

```bash
terraform init
terraform plan -var "acces_key=$minio_access_key" -var "secret_key=$minio_secret_key" -var 'namespace=my-namespace' -out deployment.plan
terraform apply deployment.plan
```

Once Terraform is complete, the React application will be available from your Kubernetes cluster via Ingress. (i.e. `http://host.docker.internal/`)

## References
- [minio docs](https://docs.min.io)
- [Configuring a Static Website with Minio](https://gist.github.com/harshavardhana/f05b60fe6f96803743f38bea4b565bbf)