Angular + Firebase Hosting + Gitlab
Tags: angular, gitlab, firebase, hosting
Published: 2020-10-17 10:35:30

So in the short guide, I'm going to show you how I deploy an angular site to Google's firebase hosting using Gitlabs CI/CD Pipelines to do all the build and deploy for me.

I'm also going to be doing a staging / production builds, this way I can see things in staging and test/play and when i'm happy, push my changes to the repo's `main/master` branch and Gitlab will deploy it all to production.

Ok, so first thing, we need the Firebase CLI installed, using the following command in your terminal:

```bash
npm install -g firebase-tools
```

Once that is all done, head over to the [Firebase Console](https://console.firebase.google.com/) and create 2 new projects: 

For this demo, I created 2 

`fir-hosting-demo-staging`
and
`fir-hosting-demo-prod`

You need to have 2 seperate projects if you want to deploy to staging and then production, if you don't, just create the one project.

Once they're created, head back over to your Angular application, we've got a bit of preparation to do.

Navigate to your angular application using a terminal and run the following command:

`firebase init`

Highlight the `hosting` option and then the `Use an existing project` option.

Select your *Staging* project from the list that you get by using the arrow keys.

You'll then be asked the question
```bash
What do you want to use as your public directory?
```

This is the path that is set in the `angular.json` for `outputPath`, it's the path that everything goes to when you run `ng build`.

For me, it is `dist/firebase-hosting-demo` as that is the name of my Angular application. So set it to that.

You'll then be asked about redirects and as its an angular application, you'll want to select `yes` to redirect everything to `index.html`.

Once you have ran this command, open up the newly created file called `.firebaserc` and it should look something like this:

```
{
  "projects": {
    "default": "fir-hosting-demo-staging"
  }
}
```

I like to rename my project alias, so go ahead and change `default` to `staging`

```
{
  "projects": {
    "staging": "fir-hosting-demo-staging"
  }
}
```

We're now going to add our production hosting, run the following command in the terminal.

`firebase use --add`

You'll then be given a list of your current firebase projects, select the production one you created.

You then have to give it an alias, I like to call mine `production`.

If you head over to the `.firebaserc` file, it should now look like this:

```
{
  "projects": {
    "staging": "fir-hosting-demo-staging",
    "production": "fir-hosting-demo-prod"
  }
}
```

You can now in the terminal of that project, just run

`firebase use staging` and it will switch to use the staging project, same for `firebase use production`.

Now, if everything has been setup right, you can go ahead and do a test build/deploy.

In your terminal, make sure you're using `staging` by running 
`firebase use staging`

Build your Angular application using the command
`ng build`

Once that has built, you should have a `dist` folder with your application inside, for me it's `dist/firebase-hosting-demo`.

Go ahead and run the command `firebase deploy --only hosting` and watch as Firebase will now take all the built files from the output folder and upload them to firebase staging branch.

Once that has completed, the firebase cli will return a Hosting URL where you can see you staging application.

You can do the same for the production branch buy running the command to switch to production `firebase use production` and then the same `firebase deploy --only hosting` and the same files will be pushed to the production project in firebase.

This is good for testing, but it will become a bit tedious to remember which branch you're on, which project you're using and remembering that you need to build/push your code.

This is where we're going to leverage Gitlab and let it do all of the work for you.

Other CI/CD tools can do the same, I just mainly use Gitlab.

So now this is all setup, let's move onto the next step!

# Gitlab

If you have your code all stored in Gitlab, the way I like to work is have 3 branches, `master`, `staging`, and `develop`.

Both `master` and `staging` are protected branches and cannot be pushed to locally, only via a merge request using Gitlab.

So, for me, I'm going to switch to the `develop` branch of my code.

`git checkout -b develop`

We're going to need a CI Token, this is a firebase token that Gitlab can use to act on our behalf. To get one of these, in your terminal, run the command

```
firebase login:ci
```

A popup will appear in your browser to login and ask for permissions, once you have done that, back in your terminal, you'll be given a CI Token that looks a bit like this.

```
1//03s000000000000000000000F-L9I00000000000000W-_000000Tg
```

I have purposely changed mine for this demo, yours won't have lots of zeros inside it.

Head over to gitlab as we now need to store this token in the project.

In your repo in gitlab, click the *Settings > CI/CD* and expand the section for *Variables*.

Click the `Add Variable` button and a window like this will appear.

![Gitlab Variables](https://dev-to-uploads.s3.amazonaws.com/i/8x3upg8rocxa8opcu1f5.png)

Call the variable `FIREBASE_TOKEN`, and paste in the value you got from the `ci:login` before. I don't have the variable protected or masked, I have found that sometimes Gitlab has issues when I check these options.

Save the variable and we're done with this part of the setup.

Back into our code, create a new file at the `ROOT` level of your project called `.gitlab-ci.yml`. This is a file that gives Gitlab the instructions needed to build your project.

Paste in the following into that file:

```
stages:
    - build
    - deploy

image: node:12.13.0-alpine

cache:
    paths:
        - node_modules/

build-staging:
    stage: build
    rules:
        - if: $CI_COMMIT_BRANCH == "staging"
    script:
        # Install dependencies
        - npm install
        # Build App
        - npm run build
    artifacts:
        paths:
            # Build folder
            - dist/firebase-hosting-demo
        expire_in: 1 hour

deploy-staging:
    stage: deploy
    script:
        - npm install -g firebase-tools
        - firebase use --token $FIREBASE_TOKEN staging
        - firebase deploy --only hosting -m "Pipeline $CI_PIPELINE_ID, build $CI_BUILD_ID" --non-interactive --token $FIREBASE_TOKEN
    rules:
        - if: $CI_COMMIT_BRANCH == "staging"

build-production:
    stage: build
    rules:
        - if: $CI_COMMIT_BRANCH == "master"
    script:
        # Install dependencies
        - npm install
        # Build App
        - npm run build
    artifacts:
        paths:
            # Build folder
            - dist/firebase-hosting-demo
        expire_in: 1 hour

deploy-production:
    stage: deploy
    script:
        - npm install -g firebase-tools
        - firebase use --token $FIREBASE_TOKEN production
        - firebase deploy --only hosting -m "Pipeline $CI_PIPELINE_ID, build $CI_BUILD_ID" --non-interactive --token $FIREBASE_TOKEN
    rules:
        - if: $CI_COMMIT_BRANCH == "master"

```

I'm not going to go over in depth what this file does, but essentially, it will build and deploy your Angular application for you, but use different Firebase projects depending on what Git branch you use.

So the idea is, make some code changes on your `develop` branch. Once you're happy, commit and push your code to the remote develop branch.

Do a merge request when you're ready from `develop` to `staging` and watch as Gitlab will start a pipeline to build your Angular app and deploy it to Firebase.

When you're happy to go live, do another merge request from `staging` to `master` in Gitlab and it will build again and deploy to production.

The first time this runs it might take some time, this is because in my `gitlab-ci.yml` file, I have specified to cache the `node_modules`, but once this has done, pipelines that run after this should be faster!

Hope you enjoyed my guide, and any questions, don't hesitate to contact me!.