Remix - Deploying to Google App Engine (Part 4)
- Part 1 - Getting started with Remix
- Part 2 - Adding Firebase Authentication
- Part 3 - Adding Firestore Access
- Part 4 - Deploying to Google App Engine ← you are here
- Bonus! Part 5 - Deploying to Vercel
Instructions to deploy a Remix application using Firebase and App Engine Vercel. I'll be using vanilla JavaScript, but feel free to implement your solution in TypeScript if you wish. Please also note, I won't be building a full application, but rather exploring the integration of Remix with Firebase as a means to an end. There will be plenty of undeveloped tasks for the reader to complete.
UPDATE (February 10th, 2022): I've created a starter project based on the initial exploration I did in this walkthrough series. You can find my more structured TypeScript implementation on GitHub: https://github.com/nathanhumphrey/remix-app. The repo README provides a high-level overview of the details, and the project comes with some sample impementations.
Skip it! #
TL;DR - long story short, I didn't get the application to deploy to Google App Engine. I ran into problems, I believe, with the postinstall
script, which were corrected by moving the @remix-run/dev
package from devDependencies
to dependencies
, but then there was an issue with running node
or issues with the application trying to write to the filesystem. I spent some time digging into the guts of Remix to try to find a solution, but it's the holiday season, and I don't want to spend any more time on this right now than I have to. So, I'm abandoning App Engine deployment for now. I may come back to it when I have more time in the future (I have trouble letting go of things). Check out the next (bonus) part of the series for the Vercel deployment.
Introduction #
Okay, we've made it to the final part of this Remix series: deployment. Remix can easily be deployed to several different hosting platforms or services. I'm choosing App Engine simply because it's what I'm comfortable with, and it isn't listed as one of the target options that Remix has out of the bag. My hope is that this will be a short post.
The default setup for Remix builds the application in the /build
and /public/build
directories. We need to perform the following tasks to deploy to App Engine:
- Pre-req's: Google account
- Ensure our Firebase project has both Authentication and Firestore enabled
- Be sure to create the Admin user and the secrets collection/document for testing
- Create an App Engine project
- Configure the deployment via the necessary yaml file
- Deploy our build directories and package.json file to the cloud
- Let it build, hopefully without error
- Access the app!
Let't go!
Create the App Engine project #
Open Google Cloud Console, select App Engine from the Resources section, and get started.
I'll choose Node.js and the standard environment for this walkthrough. If you don't have it installed, you will need to install the Google Cloud SDK. You can find installation directions online. Log in with your Google account and then run the gcloud init
to initialize a new gcloud configuration.
# log in with your google account
$ gcloud auth login
# then init your gcloud config
$ gcloud init
If you already have a default configuration, be sure to choose Create a new configuration
option when running the command. Give your new configuration a name (this name will be used locally when managing your gcloud configurations). Then, choose to log in with your Google account. You should then see a listing of available projects, including the Firebase project id you set up in part two of this series. Let's keep everything together in one project and choose the existing Firebase project from the list. When you're done, you should be able to run the following command to see a listing of your current configuration:
gcloud config list
If you see a listing with your Firebase project ID, then we're ready to move on.
Configure App Engine #
Before jumping to deployment, we need to add some additional configuration options, so App Engine will know how to run our application. App Engine will look for an app.yaml
file for all the project's configuration options. Create an app.yaml
file in your project directory and add the following contents:
# app.yaml
runtime: nodejs16
Note: as of the date I'm writing this walkthrough, Node.js 16 is the latest stable release supported.
At a minimum, we need to configure the target runtime for our application, nodejs16
, in this case. In addition to the runtime environment, we also need to specify several environment variables that our application will require. Make the following additions to set the instance class (F1 to keep our costs in the free tier) and required environment variables:
# app.yaml
runtime: nodejs16
instance_class: F1
env_variables:
GOOGLE_APPLICATION_CREDENTIALS: './firebase-admin-key.json'
SESSION_SECRET: 's3cret'
GCLOUD_PROJECT: 'remix-app-XXXXX'
Note: there are many articles written about how to manage sensitive environment variables for deployment. I include the service account file within the project directory in this walkthrough. Still, you should read more about this topic and settle on a solution that you feel comfortable implementing.
Just as we needed some environment variables for our local testing, our application will need these same variables in production. Note that the path to the application credentials is relative to the location of the project directory. We need to do this because we don't control where files will be placed on the App Engine resource. By default, App Engine will run our application with the package.json run script, so we don't need to do anything special concerning application startup.
The final settings we'll set in this file have to do with the static assets that will be uploaded. Remix builds the application into two directories: 1) build, which contains the application, and public/build, which contains the static assets. We can inform App Engine of our static assets and desired URL in the app.yaml file as well
Note: it's possible to configure your static assets to be served from another service (e.g. cloud storage), but that's beyond the scope of this walkthrough.
# app.yaml
runtime: nodejs16
instance_class: F1
env_variables:
GOOGLE_APPLICATION_CREDENTIALS: './firebase-admin-key.json'
SESSION_SECRET: 's3cret'
GCLOUD_PROJECT: 'remix-app-XXXXX'
handlers:
- url: /build
static_dir: public/build
Configure App Engine ignore in a .gcloudignore
file.
Enable Auth and Firestore on Firebase #
Visit the project you've created on the Firebase console and enable both Auth and Firestore (if you haven't already done so).
Perform a config dry-run with the App Engine dev server [OPTIONAL] #
Test the config settings locally first!
Allow your local application to temporarily use your user credentials for API access:
gcloud auth application-default login
Warning: if you still have the GOOGLE_APPLICATION_CREDENTIALS
environment variable set from a previous part, unset it first. Don't worry, we'll deal with Firebase admin access shortly.
Once done, startup the App Engine dev server:
dev_appserver.py --application=remix-app-XXXXX app.yaml
Warning: if you want to run dev_appserver.py with the Python 3 interpretor, you need to specify the --runtime_python_path=[PATH_TO_PYTHON3_BINARY]
flag. For example: dev_appserver.py --runtime_python_path=/usr/bin/python3 --application=PROJECT_ID app.yaml
. Or, you could use something like pyenv
to manage your python environment.
You will be prompted to install a few Google Cloud emulators, accept and continue. Because the app runtime is set to nodejs, the dev server will fail, but it will spit out any errors you have with your app.yaml
file, so that's how I'll make use of it.
Deploy to App Engine #
When you're ready to deploy, run the following command:
gcloud app deploy
You will be prompted to select a region for deployment. Choose the same location as your Firebase project if you set a location for Firestore. Note, two locations that will create multi-region buckets: us-central
and europe-west
. I'm choosing northamerica-northeast1
. If you haven't, you will need to enable Cloud Build and billing for the project before your app will deploy correctly; don't worry, you get up to 120 minutes of build time per day for free.
Stop #
Summary #
The postinstall
script caused problems on App Engine, so I removed it from the package.json
file and redeployed it. However, when the postinstall script was removed, I then received a /usr/bin/env: 'node ': No such file or directory
message in the logs. I don't know why, and I spent too long trying to figure it out.
Long story short, I couldn't get my Remix app deployed to App Engine. It was first something to do with the postinstall
script (had to move @remix-run/dev
to the dependencies
section to enable the required CLI script), but then GAE couldn't find and run node
... frustrating. Quite frankly, I don't have the time to spend digging through the source (any more than I already have) to try to find what the problem might be. For now, I think a hobby free-tier deployment to Vercel might be a better option, given that it's something Remix supports right out of the box. In another post, I'll document my adventures with Vercel, using this same project; I've never used Vercel before, so it should be fun.
References #
- Remix Deployment
https://remix.run/docs/en/v1.0.6/guides/deployment - Google App Engine
https://cloud.google.com/appengine - Google Cloud SDK Install
https://cloud.google.com/sdk/docs/install - Google App Engind - Quickstart for Node.js in the standard environment
https://cloud.google.com/appengine/docs/standard/nodejs/quickstart