How to deploy a Dockerized React/TS app in 10 minutes with Koyeb
Tags: typescript, react, docker
Published: 2022-01-28 14:58:13

Hola fellow devs!

If you landed here, it's probably because you know too well how painful and boring it can be to deploy a fully functional React application.
Writing code "is easy", bringing it to life for the first time however might seem scary.
I hope that once this reading is done, serving and deploying your app will no longer be a problem.

**_Spoiler alert_**: I'm quite lazy, so we'll stick to the essential meaning we'll build our app on top of `create-react-app` and I'll assume all the pre-"pre-requisites" are checked (docker concepts, npm and npx installed etc.)

Sooo, what will we do today? 

To focus on deploying our application, we'll keep simple  objectives:
- Pop a basic typescript app
- Write and understand our dockerfiles
- Publish our app image on dockerhub
- Deploy our app with [Koyeb](koyeb.com)


## Requirements

- [A dockerhub account](https://hub.docker.com/signup)
- [Docker running on your machine](https://docs.docker.com/get-docker/)
- [A koyeb account](https://app.koyeb.com/auth/signin)


## About Koyeb in a few lines

I've been fed up of using Heroku, even though it does the job for side projects (AWS and GCP a bit overkilled), it was just ... too much and always the same.
Looking for an alternative I stumbled upon Koyeb which provides a serverless platform that allows to deploy apps with low-config, auto-scaling, global scope (in other words, tons of features we won't need here :boom:)

Let's see that by ourselves


## 1) Pop the app!

Easy peasy. If as me, you've already done this a thousand of times, just skip this part :).

Let's create our project using the typescript template.

```
yarn create react-app my-app --template typescript
```

Its name says it all, this will generate a ready to use Typescript project with all dependencies installed (otherwise, don't forget to `yarn` or `npm install` at the root of your project)

Again, as usual (God I can't take it anymore :smile:), a rapid `yarn run start` should start your application on `http://localhost:3000` with the (My god, yes again) wonderful react app spinner. If not, please advise :sweat_smile:.

At this point, you are free to start writing the code you want. We won't go into any coding in this article however.


## 2) Dockerize our app

In the first version of this post, we'll go straight to a prod-ready environment. But I swear in front of all the gods, if more than 5 of you ask in the comment for a development environment with hot reload... I'll execute myself.

We'll build the dockerfile together, piece after piece. It's never easy to start one from scratch, especially when you want to focus on developing your application so I feel it's important to understand what we want and what we are doing.

First, let's create a `Dockerfile` file at the root of the project which should look like this, otherwise you cheated:
```

```
Nicely done! (Need help here, I don't know how to add the path in the codeblock)

Quick reminder. In production, we do not simply execute our modularized code. We need to build our app first (using `npm run build`). `index.html`, our entry file will be served statically. That's where and why going from a local environment to production becomes tricky.

Having that in mind, we can split into two pieces what we have to do:
1. Build our application
2. Serve our build (we'll use nginx to do so - laziness remember)

Locally we can build our project running `npm run build`. Let's see how we translate that into the `Dockerfile`:

```docker
# 1st step: The build

# Here we state that we will be using the node 16.10 version as the base image
FROM node:16.10 as build
# We define /app as our working directory -where our incoming commands will be executed-
WORKDIR /app

# We copy our package.json and yarn.lock (adapt if you are using npm to package-lock.json) into our workdir
COPY package.json ./
COPY yarn.lock ./

# We install our dependencies
RUN yarn
# We install react-scripts globally to avoid any bad surprise
RUN yarn add react-scripts@3.4.1 -g

# COPY our app
COPY . ./

# And we build! -yarn comes with the node:16.10 image-
RUN yarn run build

```

Alrighty, our build is up and not running. As we said, next step will now consist in mounting a webserver to serve it. Gogogo!

Let's first configure our soon to be born server. To do so, we just need to add the following config file in a new folder `nginx/nginx.conf`. I won't go into the details, up to you to deep dive into nginx :smile: so I'll directly share a working config:

```
server {

  listen 80;

  location / {
    root   /usr/share/nginx/html;
    index  index.html index.htm;
    try_files $uri $uri/ /index.html;
  }

  error_page   500 502 503 504  /50x.html;

  location = /50x.html {
    root   /usr/share/nginx/html;
  }

}
```

Now, let's go back to our `Dockerfile` and get this server up:

```docker
# ... Step 1

# Always good to repeat, we use nginx:stable-alpine as our base image
FROM nginx:stable-alpine
# Taking advantages from docker multi-staging, we copy our newly generated build from /app to the nginx html folder -entrypoint of the webserver-
COPY --from=build /app/build /usr/share/nginx/html
# We copy the nginx conf file from our machine to our image
COPY nginx/nginx.conf /etc/nginx/conf.d/default.conf
# We expose the port 80 of the future containers
EXPOSE 80
# And finally we can run the nginx command to start the server
CMD ["nginx", "-g", "daemon off;"]
```

## Quick break

Ok! For those still reading, I guess that if you landed on this article, it means you -like me- are no expert in virtualization.

So, in my opinion, this should be the right time to make sure everything is working as expected.

We can build or image running the following command `docker build . -t frontend:prod`

Take a coffee and once back, if it successfully ran, try the following command to spin up a container (same, for further explanations I'll need 10 upvotes this time):
`docker run -it --rm -p 1338:80 frontend:prod`

We use:
- `it` to run the container interactively
- `rm` is to clean up the container once we exit it
- `p` the good old port binding, `yourmachine:yourcontainer`

Boom, navigate to `http://localhost:1338/` and you should have your app up and running -locally-, congrats :tada:!

## 3) Pushing your image to Dockerhub

:warning: This will push the image on a public repository, if you do not feel at ease, you can follow this [stackoverflow guidance](https://stackoverflow.com/questions/28349392/how-to-push-a-docker-image-to-a-private-repository#:~:text=log%20into%20your%20docker%20hub,marked%20as%20private%20by%20default.) to keep it private.

I'll assume you created your docker account and remember your `DockerId`. Connect to your docker account from the shell with the `docker login` command and complete the required steps.

Let's first tag your image
`docker tag frontend:prod {YOUR_DOCKER_ID}/prod`

and push it (should remind you of git)
`docker push {YOUR_DOCKER_ID}/prod`

That should be it!

## 4) Deploying using Koyeb

Koyeb is still in an early-stage, once you have created your account, join their slack and you should be activated within a few minutes.

We'll use their [dashboard](https://app.koyeb.com/) to save time (30 upvotes for CLI).

You should land on the following page
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/42zw092b8urhqb6m7kwy.png)

Click on create an app to land on what will be of fun for us.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ec82zv86kt97zlfid4hm.png)

What's in it for us?
- Select the docker method and point to `docker.io/{YOUR_DOCKER_ID}/{IMAGE_TAG}`
- Expose our container port `80` (cf: the Dockerfile)
- Choose a name for your service

Create your service ... and TADAAA ! You shouldn't have the time for another coffee that your app should be alive, yes, alive I said (anyways, your last coffee was 5 minutes ago, it would really be unhealthy).

At the time I'm writing this post, custom domains are on their way on Koyeb. However, they will provide you with a subdomain (just like Heroku default you'll tell me).

Follow the url and here you go :).

## Conclusion

Every story (even the worst) has a conclusion. So let's have one too.

If you went through all this, well first thank you ! Feedback are always welcomed so don't hesitate to point what could be improved :smile:.

Then what have we learnt (I hope):
- Run a create-react-app command (ok, doesn't count)
- Write a simple yet functional `Dockerfile` (let's not underestimate that, the most complex ones always start somewhere)
- Build a production-ready React application with docker
- Starting a nginx webserver with docker
- Deploy a docker image using Koyeb

Wow, so much. On a more serious tone, first deployments may seem hard but in the end, splitting it into smaller steps helps demystifying them.

Especially as a developer, leveraging tools like Koyeb reduce the complexity of managing a whole infrastructure and let you focus on your field of expertise (coding I guess?) and what really matters: your users.

Hope this helped!

And quoting the good old Johnson, what a hell of a ride!