Continuous Deployment For React Apps on Shared Hosting

Sunday, June 20, 2021

By Etin Obaseki

This article walks through setting up a continuous deployment pipeline from a GitHub repository to a shared hosting’s public directory for React apps.

Although shared hosting is considered uncool nowadays, it remains a massively popular option especially because it is really cheap.

Following this guide will help bring one of the biggest comforts of more modern workflows to shared hosts.

Set up FTP

We’ll need to set up FTP on our shared host.

If your shared host uses cPanel, the steps would be;

Login to your cPanel account via

Search for “FTP Accounts”

Create a new account. I typically like to use as the account name. Set the home directory for this account to the public root (public_html) for the website.

Copy the password you’ve chosen to clipboard and take note of the FTP Settings such as the server address (typically and server port (21)

You can test that this configuration works by connecting with these credentials using an FTP client like FileZilla.

Set up GitHub Repository

If you don’t already have a GitHub repository set up for your project, you can go over to and create an account if necessary before setting up a new repository.

Here’s a quick guide on getting started with Git and GitHub if you’re interested.

After you’ve pushed to your repository we can now proceed to set up the GitHub action.

Set up GitHub Actions Workflow

GitHub Actions allow you to automate or manage parts of your development lifecycle. You can set up Actions to trigger under specific conditions for a repository and then perform some action. Common use cases involve running tests and, like we are doing today, automating deployments.

Our first step is to create a Repository Secret. Secrets hold sensitive information that we need for our actions but should not be exposed in the code. They’re environment variables for our Actions.

Go to the Settings tab of the repository and then Secrets tab. Click on New repository secret. Name the new Secret FTP_PASSWORD and put in the FTP Password from the shared host as the value. You may also create a Secret for the FTP Username, but I put this into the Action code since it isn’t as sensitive.

In the root directory of the repository, we’ll create a directory with the path .github/workflows and within it create a main.yml file with the following code:

# This is a basic workflow to help you get started with Actions

name: Build and Deploy React App via FTP

# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the master branch
branches: [ master ]

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
# This workflow contains a single job called "build-and-deploy"
# The type of runner that the job will run on
runs-on: ubuntu-latest

# Steps represent a sequence of tasks that will be executed as part of the job
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2

# Runs a single command using the runners shell
- name: Run react build script
run: yarn && yarn build
CI: false

# Runs a set of commands using the runners shell
- name: FTP-Deploy-Action
uses: SamKirkland/FTP-Deploy-Action@3.1.1
ftp-password: ${{ secrets.FTP_PASSWORD }}
local-dir: build/

The /.github/workflows/main.yml file (don’t forget the dot before ‘github’!) defines the workflow for our action. The name key is a human readable name for the Action and is displayed in the Actions tab of the repository when it is run. The on key specifies the trigger for the Action. In this case, we’ve specified it as on Push to the main branch.

A workflow run is made up of one or more jobs that can run sequentially or in parallel. This workflow contains a single job called “build-and-deploy” although, we could specify any name we wanted for it. The steps key contains a sequence of tasks that will be executed as part of the job. Each entry (represented in YAML by a dash -) consists of a few fields including name, run, env, uses and with.

The name key is the human readable name for the task. run specifies a command to run as part of the task and env key specifies environment variables to be set as part of the shell command to be run.

Instead of running a shell command as a step in our job, we could also call on another GitHub Action to run within the context of our job. The uses key specifies the GutHub Action to run and is in the format <GITHUB_USERNAME>/<REPOSITORY_NAME>@<VERSION_NUMBER>. The with key specifies variables to be passed into the Action being called.

The first step in our job uses the first party GitHub Action checkout to pull in our repository and made it available to other steps on the job. Next we run the command yarn && yarn build to install our dependencies and generate a production build. We also set the environment variable CI to false so that errors are not treated as warnings, but if this is behaviour you would rather have can be left off. We now have a production build of the app and can deploy this via FTP to our shared host. To do this, we use another GitHub action FTP-Deploy-Action written by Sam Kirkland and pass in FTP Host, Username, Password and directory to deploy as variables using the with key.

We should now have the FTP action trigger on code push. However, because the build/ directory where the production files are housed is untracked in git, it may be ignored for upload as the files will be considered to not have changed. To combat this, we need to create a .git-ftp-include(Again, don’t forget the leading dot) file in the root of the project and include the following line:


This will ensure that the build folder is uploaded via ftp even though it is untracked in git. The manual entry for git-ftp in Ubuntu provides other ways the file can be used.

Redirect to Index

Now that we can deploy our app on code push, we should have the app running on the shared host. A common issue with single page applications is the need to redirect all incoming requests to index.html. On a shared host we will do this via the .htaccess (you know the drill with the dots by now) file.

RewriteEngine On
RewriteCond %{SERVER_PORT} 80
RewriteRule ^(.)$$1 [R,L] RewriteEngine on RewriteCond %{REQUEST_URI} !^/$
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond {REQUEST_FILENAME} !-f
RewriteRule ^(.)$ / [L,QSA]

Create a file named .htaccess with the above code in the public/ directory of your app. This ensures that when the production version is built, the file is copied into the build/ folder.


With this, we have now set up a continuous deployment pipeline from a GitHub repository to a shared hosting’s public directory for React apps. Whenever we push code to the main repository, a deploy will be triggered and the site on our shared host will be updated.