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
Requirements
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 π₯)
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 π), 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 π
.
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:
- Build our application
- 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
:
# 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 π 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:
# ... 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 π!
3) Pushing your image to Dockerhub
β οΈ This will push the image on a public repository, if you do not feel at ease, you can follow this stackoverflow guidance 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 to save time (30 upvotes for CLI).
You should land on the following page
Click on create an app to land on what will be of fun for us.
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 π.
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!
Top comments (2)
Wow, amazing. It's crystal clear! Thanks a lot
Thanks bro for your support! It lightens my heart :)