This is a work-in-progress step-by-step guide to deploying a SvelteKit app as a
Google Cloud Run service using GitHub Actions and Docker.
This can all be done entirely for free, but each of these services have a
certain free quota, and you will be charged if you go above it. The free quota
should be more than enough for most hobbyists and individuals.
Additionally included in this project for demonstration purposes are:
For convenience, the app also uses TypeScript and a .editorconfig
file.
Use the following command to create a basic SvelteKit app in the my-app
directory. You can skip the directory name at the end of the command to create
the app in the working directory.
npm create svelte@latest my-app
Alternatively, use any other way to scaffold a SvelteKit project, like my personal favourite Skeleton UI, or use this repository as a starting point.
Once you've created a project and installed dependencies with npm install
(or
pnpm install
or yarn
), start a development server:
npm run dev
You can use whichever package manager you like. For this example I've stuck with
NPM since it is the most widely used, but to change which one you use just
delete package-lock.json
and tweak main.yml
and Dockerfile
. To switch to
PNPM for example, replace these lines in main.yml
:
run: npm ci
# ...
run: npm run test
# ...
run: npm run build
# ...
run: cp package*.json Dockerfile build
with:
run: npm i -g pnpm; pnpm i --frozen-lockfile
# ...
run: pnpm test
# ...
run: pnpm build
# ...
run: cp package.json pnpm-lock.yaml Dockerfile build
and this line in Dockerfile
:
RUN npm ci
with:
RUN npm i -g pnpm && pnpm i --frozen-lockfile
First install Playwright's dependencies:
npx playwright install --with-deps
Run your tests locally:
npm run test
# or to run only unit tests
npm run test:unit
# the same for integration tests
npm run test:integration
In the case that you've used this repository as a base, you must either replace
the environment variables in .github/workflows/main.yml
, or add the variables
to your GitHub repository secrets under "Settings" > "Security" > "Secrets and
variables" > "Actions" > "Variables".
For server-only, secret environment variables, add them to your GitHub
repository secrets, under the "Secrets" tab in the same places as the variables
above.
GCP_PROJECT_ID
: a variable containing your GCP project ID. This example uses
"sveltekit-gh-actions-cloud-run". GCP project IDs are globally unique, so if you
choose something less unique, you may get some numbers appended to yours.GCP_SERVICE_ID
: a variable containing the ID of your Cloud Run service. This
can be anything you want. This example uses "app".GCP_SERVICE_REGION
: a variable containing the GCP region in which your Cloud
Run service will be hosted. Select the region closest to you/your users. This
example uses "us-central1".GCP_SERVICE_ACCOUNT_KEY
: a secret containing a JSON key for a service
account with editor permission in your GCP project. How to get this key is
explained below in the Google Cloud Platform project setup section.The GCP_PROJECT_ID
, GCP_SERVICE_ID
, and GCP_SERVICE_REGION
variables can
be replaced inline with your GCP project ID, your Cloud Run service ID, and
the service region without any issues, as they are not sensitive. Do note that
the service region cannot be changed once set.
The GCP_SERVICE_ACCOUNT_KEY
secret must be added to your repository secrets,
as it is extremely sensitive.
The GitHub workflow can be found in .github/workflows/main.yml
. It contains a
job called test-build-deploy
. The job runs on pushes to the main branch. It
sets up the environment, runs the tests, builds the app, and deploys it to
Google Cloud Run. This workflow is pretty basic and has lots of room to be
improved and expanded upon.
The second-to-last command is a complicated mess that just deletes old unused
versions of your app from Artifact Registry. This is done because Google will
begin to charge you if you store more than 5 gigabytes there, and there is no
easier way to delete unused images. You don't need to understand it, just make
sure that if you replace the repository variables inline, you do so here as
well.
The Dockerfile is super basic, as Vite does all the hard work getting the app
production-ready. All it does is set up a Debian image running a long-term
support Node.js version, install production dependencies, and run index.js
in
the built app as the preconfigured "node" user.
The "node" user does not have access to the file system, so if you need to save
files on the server (NOT recommended!), delete this line.
A few manual steps will be necessary for this pipeline to work, but once
they've been done once, they will never need to be touched again.
When following a link in this section, make sure that you check that
your GCP project is selected at the top left of the screen.
This can be done through the Google Cloud Console under "IAM & Admin" > "Service
accounts". Direct link here:
https://console.cloud.google.com/iam-admin/serviceaccounts. Click the "Create
service account" button at the top. Give it whatever name you want (I usually go
with "GitHub Actions Deployer"), and give it the "Editor" role found in the
"Basic" category. Back in the service accounts overview, click on the new
service account, go to the "Keys" tab, and create a new JSON key. Copy the
contents of the JSON file into a new GitHub repository secret called
GCP_SERVICE_ACCOUNT_KEY
.
Go to "APIs & Services" and click on the "Enable APIs and services" button at the top, or go to https://console.cloud.google.com/apis/library and search for these APIs. Select them, and enable them. You may be prompted to upgrade your plan to pay-as-you-go, but the free quotas are quite generous and you can set alerts to go off if you go above them.
After the first successful deployment, you may get a 403 response if you try to access the service URL of the deployed Cloud Run service. To fix this, go to your Cloud Run service in the Cloud Console and in the "Security" tab, switch the "Authentication" setting from "Require authentication" to "Allow unauthenticated invocations".
Things that can be done to expand upon this pipeline:
If you want a nicer URL than the service URL created for your Cloud Run service
(like https://app-magdwkhvxi-uc.a.run.app for example), or if you even have your
own domain you want to use, then the easiest way is to set up Firebase Hosting.
Firebase is included for free with your GCP project, but can't be found in the
Google Cloud Console. Go to https://console.firebase.google.com and go through
the steps to set up your GCP project with Firebase. Once Firebase is set up,
find the "Hosting" section in the "Build" dropdown, and create a site.
With Firebase Hosting enabled in your project, and a site created, create these
two files in the root of your SvelteKit project.
.firebaserc
{
"projects": {
"default": "your-firebase-project-id"
}
}
firebase.json
{
"hosting": {
"site": "your-firebase-site-name",
"rewrites": [
{
"source": "**",
"run": {
"serviceId": "your-cloud-run-service-id",
"region": "your-cloud-run-service-region"
}
}
]
}
}
Make sure serviceId
and region
here match your Cloud Run service's chosen ID
and region. This repository uses "app" and "us-central1".
Install the Firebase CLI.
npm i -g firebase-tools
Then run this command to apply the rewrite. This will only need to be done once.
firebase deploy --only hosting:your-firebase-site-name --project your-firebase-project-id
The GitHub Actions Deployer service account you created will have far more
permissions than it needs. Your Cloud Run service will also run as the default
compute service account, which will also have far more permissions than your
service needs. Ideally, you should go back to IAM after a few weeks and
several deployments, and fix this.
Your GitHub Actions Deployer should have its unused permissions removed, and you
should create a new service account for your Cloud Run service to act as.
Read more about best practices for service accounts at
https://cloud.google.com/iam/docs/best-practices-service-accounts.