How to write a C# backend for a Unity game using Firebase and Google’s Cloud Run
Google Cloud recently launched Cloud Run as a way for you to deploy stateless containers as a cloud backend. Given that these are built on top of Kubernetes, you can use any combination of frameworks and languages you want (as long as you can Dockerize it). You just need to listen to the port specified by the
$PORT environment variable and, for our purposes, you need to respond within 60 seconds.
Since I get my run of languages, I immediately decided to try to get C# running so I can use the same language in Unity and my backend. After some research, I decided to settle on .NET Core and Kestrel for my setup.
As of this writing, Cloud Run is beta. After installing the Cloud SDK, you’ll have to install the beta components by typing:
gcloud components install beta gcloud components update
If you have an existing Firebase project, you should have a corresponding cloud project at console.cloud.google.com by default. If you don’t see your project under “Recent”, try checking the “All” tab or just search for it by name.
You can now add Cloud Run to your project by selecting it in the side menu and clicking “Start Using Cloud Run”. You will need billing enabled, but the console will walk you through turning that on if you haven’t done so already.
Now we’re ready to start setting up your project! Move to an empty directory where you want to work and type:
to make sure you’re using your account and to select your cloud project.
Once you have your gcloud account setup, you should create a quick project to upload. Since you’re going to be using .NET Core, you should install the corresponding SDK on your computer. Now, cd into an empty directory then type into your console:
dotnet new web
to create a project and:
to verify that it works. If you open your browser to the port indicated, you should see “Hello World.”
It’s time to get this ready for use in Knative now. The generated code is nearly ready, we just need to read the
$PORT environment variable for this all to work. To do this, change:
Now that your web app is ready to work with Knative, you need to create a Dockerfile to containerize it. Create a file in your project directory named
Dockerfile. Then type the following:
"Backend.dll" to the name of your project’s artifact. If you’re unsure what that is, it should match the name of your
I’ll break this down a little bit. This uses Microsoft’s provided dotnet build environment and runtime with:
FROM microsoft/dotnet:sdk AS build-env
build-env, the first
COPY instruction and
dotnet restore make sure you have all the proper dependencies. Then we
COPY in our entire project and
dotnet publish it. Finally you copy the
/out directory into the runtime environment and execute the command
dotnet Backend.dll when it’s time for your container to run.
If you’ve done everything correct up until this point, you will be able to use gcloud to build your project by typing:
gcloud builds submit --tag gcr.io/$PROJECT_NAME/$CONTAINER_NAME
In my case, my
flappyfirebird and I want my Cloud Run container to be named
backend. So I typed:
gcloud builds submit --tag gcr.io/flappyfirebird/backend
If you don’t see any errors, it’s time to upload the built container! Type:
gcloud beta run deploy --image gcr.io/$PROJECT_NAME/$CONTAINER_NAME
so, again, my command is:
gcloud beta run deploy --image gcr.io/flappyfirebird/backend
Right now the only available region is
us-central1, so don’t be worried if you see that as your only region to deploy to.
When asked for your service name, enter whatever you want. I left mine as the default.
When asked to allow unauthenticated invocations, answer
y for yes (this is NOT the default).
Allow unauthenticated invocations to new service [backend]?(y/N)? y
It should take a little while, but you should eventually see the text
Done, and a link to your service. Test it out by following the URL provided:
Congratulations, you’re now running C# in a Cloud Run container! Now it’s time to setup Firebase Hosting.
Navigate over to the Firebase Console, and select your project. If you created your project in the Google Cloud Console first, you’ll have to click “Add project” and select the corresponding project from the dropdown.
Your project must be the Blaze “pay as you go” plan since you enabled billing in the Google Cloud project.
Now that you have a project setup, open your terminal and navigate to an empty directory for your Firebase project. Make sure that you have the command line tools installed, then type the command:
Use this menu to initialize Hosting, and optionally any other Firebase products you want to use:
After selecting the Firebase project you’re configuring, you can leave everything else as the defaults. You can type:
to make sure everything is working.
The final piece of this puzzle is to point some paths over to our Cloud Run instance. Open up
firebase.json and add a new entry under
"rewrites". I’ll start by routing
"/helloworld" to my Cloud Run instance:
✔ Deploy complete!Project Console: https://console.firebase.google.com/project/flappyfirebird/overviewHosting URL: https://flappyfirebird.web.app
If you execute
firebase deploy, you can verify that it’s working by appending
/helloworld to the provided URL. In my case, this means that I type https://flappyfirebird.web.app/helloworld into my address bar.
For bonus points, I’ll quickly integrate all of this into Unity. I wrote a really quick test MonoBehaviour called
As expected, it logged:
Response: Hello World!UnityEngine.Debug:Log(Object)<RunTest>d__1:MoveNext() (at Assets/Scripts/TestCloudRun.cs:18)UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr)
Adding Custom Logic
Now that we have code that we can read in Unity, let’s make something a little bit more dynamic. Rather than a simple “helloworld”, let’s log the time you hit the server. To get started, we need to add a new route to our server. Add a map to the endpoint
"/time"to point to a
Then implement a function that just returns the current time:
You can test this with
dotnet run, going to
localhost:5000/time. Then just build and deploy with
gcloud builds submit and
gcloud beta run. Note that you still can’t access it by going to
/time on your Firebase Hosting site like you can with
We can open
firebase.json and add
/time as another path to our Cloud Run container, but instead we’ll use a wildcard to catch all requests and direct them:
Although this may look like I’m redirecting all traffic to my Cloud Run container, thus mostly negating the use of Firebase Hosting, this isn’t as all-encompassing as you may think. Notably:
- Static assets always take precedence over rewrites, so you’ll still be able to get to your index.html.
- Rewrites are applied in order. If you had a few that went to a different container or a Cloud Function, they’ll be hit instead of ”/*” if they match an incoming request first.
If you go to your site’s
time endpoint (for me this was https://flappyfirebird.web.app/time), you should see the current server time! If you refresh the page you see… the same time as before. Why is this? If you
run curl -vs https://flappyfirebird.web.app/time you’ll see the entry
cache-control: max-age=3600. This means that you’ll have to wait an hour before seeing a new time!
Controlling Firebase’s Content Delivery Network
Firebase Hosting will cache whatever you put in your cache control header, so let’s do that for our time endpoint. Kestrel has the concept of Middleware, which is a system with which you can augment or modify the response for an incoming request. You can modify how long Firebase caches the response to a user’s request by modifying the CacheControl header with a simple piece of custom middleware (which I’ve listed below). I opted to put this in the
HandleTime function, since I don’t mind caching the text “Hello World” for an hour, but where you put it is up to the needs of your service.
Where do you go from here?
My goal is to build a simple cloud backend for a small Unity game I’m building, but you might have other needs. To tie into other Firebase services, you should look into the C# admin SDK and the Firebase REST API. There are also a number of excellent open source community projects available on NuGet to bridge the gap between the official admin SDK and REST. Note that even though we provide a Unity C# SDK, this is intended for clients and will probably not work in your server environment. The stable client SDKs are only fully implemented in iOS and Android, where it relies on native features of each OS to improve the end user experience.