In this blog post, you’ll learn how to serve an Angular app’s static files from Google Cloud Storage.
This post is part of the Dockerized Django Back-end API with Angular Front-end Tutorial. Check out all the parts of the tutorial there.
In the last part of the tutorial, we’ve learned how to deploy a Docker container on a VM in Google Cloud Platform. In this blog post, we’ll see how we can upload our Angular app to Google Cloud Storage and access it from our production Compute Engine VM.
Why Serve Static Content Separately?
In production environments, it’s generally best practice to serve static content separately from your app, either in GCP or using a third-party Content Delivery Network (CDN).
The reason for this is that serving static files from the app server itself can take up resources that would be better used for dynamic requests.
It’s better from a resource management point of view if the app server handles dynamic requests exclusively.
As a plus on the GCP side of things, it’s worth mentioning that Cloud Storage works as a CDN. By default, any publicly readable object is cached in the global Cloud Storage network.
Prerequisites
This tutorial is based on the documentation notes for Cloud Storage, where it’s described how to host static assets for a website.
The notes and this tutorial too assume you have registered a domain name. For simplicity, we’ll use the domain example.com
, but replace this as appropriate with your own domain.
If you got to the stage where you’re ready to deploy your app in production, then you’ve probably already registered a domain name, so you’re good to go.
If not, you can still use direct links to Cloud Storage objects, but the typical pattern is to have your own domain or subdomain with a DNS record pointing to Cloud Storage.
Pointing the Root Domain Name to the Google Cloud VM
First of all, we need to point our domain name to the Compute Engine VM where we’re running the Django Docker container.
Assigning a static external IP address to the VM
Before pointing the root domain name to our VM, we first need to ensure that the VM has a static external IP address.
By default, the Compute Engine VMs have ephemeral IP addresses, which means that the IP address of the VM can change. If we’re working with domain names, then we need to ensure the IP address of the VM remains the same.
If you have already reserved a static external IP address, then do the following:
- Go to the VM instances page.
- Click the instance on which you’re running the Django container.
- Click Edit.
- Under External IP select a static external IP address if you’ve already reserved one.
If your instance currently has an ephemeral external IP address, you can promote the ephemeral external IP address to a static external IP address by doing the following:
- Go to the External IP addresses page in the GCP console.
- In the Type column, change the address type to Static for the IP address you want to promote.
- Provide a name for the new static IP address and click Reserve.
- Optional: check that the static external IP address has been reserved by executing:
$ gcloud compute addresses list
Create an A
record pointing traffic to the VM
Now we’re ready to point the root domain to the VM’s IP address.
In your domain registrar panel, just create an A
record that points example.com
to the static IP address you’ve assigned in the previous section.
Serving Files from Google Cloud Storage
We’ll now proceed to deal with the static files.
Creating a CNAME
record pointing to Cloud Storage
Again from the domain registrar, create a CNAME
record to redirect traffic from static.example.com
to c.storage.googleapis.com
.
Creating a bucket
Next, create a Cloud Storage Bucket for static.example.com
with the same name.
- Open the Cloud Storage Browser.
- Click Create bucket.
- Add details of your choice, remembering to choose
static.example.com
for the Name.
- Click Create.
Uploading the Angular Static Files
To copy files to the bucket, you can use the gsutil cp
command. For example:
$ gsutil cp Desktop/myfile.txt gs://static.example.com
For copying a large number of files, use gsutil rsync
, optionally with the -R
option to recursively copy directory trees. For example, to synchronise a local directory named mydir
, use:
$ gsutil rsync -R mydir gs://static.example.com
Proceed to upload the Angular app’s static files in gs://static.example.com/static_angular
. This location would then be accessible from http://static.example.com/static_angular.
In the next commands, I’m uploading the Angular production (NOT development!) files from the /Users/dragos/static_angular
directory on my computer. These are the same production files copied from the Angular (i.e. ng
) container in part 10 of the tutorial, where we prepared our Angular app for production.
Make the Angular Files Publicly Accessible
Now that the files are uploaded, let’s make them public.
- Select your bucket from the Cloud Storage Browser.
- Go the Permissions tab.
- Click Add members.
- In the Members field, enter
allUsers
.
- Select Role, then select Storage, and click the Storage Object Viewer option.
- Click Add.
The Angular app’s files are now publicly accessible.
Serving the Angular App with uWSGI
At this point, the Angular files are on Cloud Storage, but how can the user actually get to them?
Well, it’s a simple matter of serving the Single-Page Application (SPA) from an index.html
file when the user accesses the homepage of the domain.
But…
We have to do this from uWSGI, the app server that’s serving our Django REST API.
Here’s how you should modify your uwsgi.ini
configuration file in django/uwsgi
:
Let’s explain the new additions:
check-static = /code/static_files
– points uWSGI to a directory of static files that it should serve. This directory will contain the index.html
file, which will essentially be our SPA.
static-index = index.html
– tells uWSGI to serve index.html
when a request for a directory is made; the default is to return 404
, because uWSGI doesn’t have built-in directory indexing.
route-if = startswith:${REQUEST_URI};/api continue:
– if the REQUEST_URI
starts with /api
, satisfy the request; the requests intended for the API will end up matching here.
route-if-not = exists:/code/static_files${PATH_INFO} static:/code/static_files/index.html
– if the requested file does not exist in /code/static_files
, serve code/static_files/index.html
instead, i.e. the Angular app.
Now we should be properly serving the index.html
file.
Create a static_files
directory within the django
directory. Now create the index.html
file in the static_files
directory as below:
This is essentially the default index.html
file that gets created when you make a new Angular app with the Angular CLI, but we’re now pointing to the JavaScript files uploaded to the Cloud Storage bucket.
Replace example.com
in index.html
with your own domain name.
Add Production Links to Auth0
Now that we’re using the app from the Google VM, we need to add the production Auth0 URLs in the Auth0 Dashboard as mentioned in part 7 of this tutorial.
So go ahead and go to the Auth0 dashboard, go to your application, and in the Settings tab, add the corresponding URLs containing the VM’s IP to the Allowed Callback URL and Allowed Logout URLs sections.
To get the code to this stage (and final), see my git commit or use:
$ git checkout v1.20
Summary
In this part of the tutorial, we’ve learned how to serve an Angular’s app static files from Google Cloud Storage. We’ve covered uploading the Angular production files to a Cloud Storage bucket. We’ve also seen how to serve the index.html
of the Angular app using uWSGI.
In the next part of the tutorial…
There’s no next part! We’re finished!
Hey, if this was useful to you, leave a comment and let me know!
If it was crap, leave a comment and let me know!
Credit: For this part of the tutorial, I’ve used the following resources: