Everything GitHub: Continuous Integration, Deployment and Hosting for your Angular App
13.01.2020
In this article we want to use several tools from the GitHub universe to launch a website with Angular. We will establish a professional pipeline, including version management, continuous deployment and web hosting. Best of all, for public repositories, this will not cost you a single cent!
Table of contents:
- Introduction
- 1. All parts explained
- 2. A simple Angular app
- 3. Hosting the source code on GitHub
- 4. A first deployment to GitHub Pages
- 5. Automating the Deployment with GitHub Actions
- 6. Extra: Custom Domains
- Summary
Introduction
All starts are hard, so we will try to explain all steps in detail, so that you can eventually come up with a fully deployed Angular app.
During our journey we will get comfortable with angular-cli-ghpages
and GitHub Actions, and see how they work together.
We will create and setup tokens and GitHub Action YAML files, to deploy our Angular app directly to GitHub Pages, a free webhosting service from GitHub.
1. All parts explained
1.1. Git and GitHub Version Control
Version Control allows you to keep track of your work and helps you to easily recognize the changes you have made, whether it is program code, text, images or other data. This is also absolutely necessary if you want to work together in a team efficiently. Git is a free software for distributed version management and is currently the most popular solution. The leading platform around Git is GitHub, which belongs to Microsoft since the end of 2018.
1.2. GitHub Actions
GitHub Actions is a solution for CI/CD pipelines (continuous integration and continuous delivery). With actions we can test, build and release our code while staying in the GitHub ecosystem. GitHub has just recently launched this product to the market and is now directly competing with Travis CI, CircleCI or AppVeyor. Similar to other state-of-the-art CI/CD tools, the individual steps are all saved to a file in YAML format.
1.3. GitHub Pages
You can use GitHub Pages to host a website directly from a GitHub repository. This website can either contain completely static content (i.e. pure HTML files and assets, but no scripts running on the server) or you can use Jekyll to convert Markdown files to static content. Since we work with Angular, we do not need Jekyll as a static site generator . A good companion for Angular is the static site generator Scully, which we will definitely cover in an upcoming article.
1.4. angular-cli-ghpages
angular-cli-ghpages is a project made by Johannes Hoppe, one of the two authors of this article. His Angular Book uses a new GitHub repository in every chapter (so there are quite a lot of repos π ). So he developed a solution that makes it as easy as possible to bring an Angular App to GitHub Pages. The project exists since 2016 and if it was started today, it would certainly have a nicer name. According to GitHub, 7100 projects already deploy their apps with angular-cli-ghpages.
1.5. GitHub plans
The following information is based on the details from the pricing page.
The free plan gives you:
- unlimited public repositories
- unlimited private repositories
- only 3 collaborators for private repositories
- 2000 total GitHub action minutes/month
The pro plan gives you:
- everything that is included in the free plan
- unlimited collaborators
- 3000 total action minutes/month
Back in 2016, GitHub migrated from a pricing based on the number of repositories to a new schema that considers the number of users. Legacy plans do not have access to GitHub Actions.
This article will distinguish several times between private and public repos. The reason behind this is due to the following point, which we must take into account:
GitHub Pages is available...
- in public repositories with GitHub Free.
- in public and private repositories with GitHub Pro and all the other paid plans.
Plan | Private repos | Actions | Pages for public repos | Pages for private repos |
---|---|---|---|---|
Free | β οΈ | β οΈ | β οΈ | βοΈ |
Pro and Higher | β οΈ | β οΈ | β οΈ | β οΈ |
Legacy paid plans | β οΈ | βοΈ | β οΈ | ? |
Please register an account on github.com, if you haven't already done so.
For this article, it is sufficient to use the free plan!
By the way, this sounds a bit like advertising. But we're not affiliated with GitHub in any way. We're just passionate GitHub users and recommend their service based on our experience.
2. A simple Angular app
We assume that the majority of our readers have already worked with Angular. However, in order for this article to be as complete as possible, we will very shortly set up a simple website based on Angular.
If you have not already done so, please install Node.js, Google Chrome and optionally Visual Studio Code. Later on we also need Git for the deployment.
Install the latest version of the Angular CLI globally and create a new Angular project.
npm install -g @angular/cli ng new everything-github-demo --defaults
Now we want to make some small changes to the source code. With the command
code .
we open the current directory in the Visual Studio Code editor. Of course you can also open the folder through the menus of VS Code.cd everything-github-demo code .
We change the content of the
AppModule
andAppComponent
a bit and call a HTTP API. By this, we want to prove that the Google search engine can correctly crawl our website β even with data loaded from remote.// src/app/app.module.ts (excerpt) import { HttpClientModule } from '@angular/common/http'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, HttpClientModule // NEW: import HttpClientModule! ], bootstrap: [AppComponent] }) export class AppModule { }
For example, we could load a book from our backend at https://api.angular.schule.
// src/app/app.component.ts import { Component } from '@angular/core'; import { HttpClient } from '@angular/common/http'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { book: any = { }; constructor(http: HttpClient) { http.get('https://api.angular.schule/book/9783864906466') .subscribe(b => this.book = b); } }
<!-- src/app/app.component.html --> <h1>Everything GitHub Demo</h1> <h4>deployed with β€οΈ and angular-cli-ghpages</h4> <p> This is some dynamically loaded content that we hope to find in the Google index later on: </p> <hr> <h2>{{ book.title }}</h2> <p> <img [src]="book.firstThumbnailUrl" width="100" align="left"> {{ book.description }} </p>
This example loads data from a remote API. Our hope is that later we will be able to find both the regular text as well as the loaded book in the index of our preferred search engine. We have also added Bootstrap CSS to make the result look like this:
3. Hosting the source code on GitHub
Now we take care of version management. This is very easy because the Angular CLI has already created a local Git repository for us.
- First things first: Let's save the latest changes.
git add .
git commit -m "chore: demo content"
Let's crete a GitHub repository. In the upper-right corner of any GitHub page, use the β drop-down menu, and select New repository.
Choose a short, memorable name for your repository. In our example, we will call it
everything-github-demo
.For now, choose to make the repository public. Please note, the source code of public repositories is visible to the public. Since we want to use the free GitHub plan, we have to keep it public or we would lose access to the free hosting service of GitHub Pages.
Since we already have source code, you should not initialize the repository with a README.
After you have pressed "Create Repository" a confirmation page will appear. You can now connect to GitHub using SSH or HTTPS. Both ways will work, but GitHub recommends HTTPS. If you are completely lost in authenticating against GitHub, try cloning another repository with GitHub Desktop first. For the remaining article we assume that the communication has been successfully established.
To add GitHub as a new remote for our local repository, use the
git remote add
command:git remote add origin https://github.com/<username>/<repositoryname>.git
Replace
<username>
(or organisation name) and<repositoryname>
with your username from GitHub and the name of your new repository. (In our example, it'sgit remote add origin https://github.com/angular-schule/everything-github-demo.git
)Finally, we can transfer the whole repository to GitHub:
git push -u origin master
4. A first deployment to GitHub Pages
We are now ready to host our first app on GitHub Pages.
For this, the project must be compiled first and the compiled assets should be pushed to a new branch.
That functionality is provided by angular-cli-ghpages
.
GitHub will activate the hosting automatically, if this branch has the name gh-pages
β which is the default setting for angular-cli-ghpages
.
The following command adds
angular-cli-ghpages
to your project.ng add angular-cli-ghpages
Now we can deploy our project to GitHub pages with all default settings. The project will be automatically built in production mode:
ng deploy
The deployment will create a temporary git repository and will commit/push all files from
dist/everything-github-demo
to a branch with the namegh-pages
.However, in this example we haven't provided a target repository. In this case the deployment assumes that the current working directory is already a Git repository, and that we want to push changes to the same repository with the name
origin
. We have prepared this before with the commandgit remote add
, so there is nothing to do here for us.The app should be available at
https://<username>.github.io/<repositoryname>/
soon. If it is not immediately available, you should first wait a moment.If we now check the results (in our case at https://angular-schule.github.io/everything-github-demo/) we will see a blank page! π² But no problem, we open the Console panel of the Chrome DevTools by pressing
Control+Shift+I
(Windows, Linux) orCommand+Option+I
(Mac). We immediately see some red 404 errors for all files:Failed to load resource: the server responded with a status of 404 ()
The reason for this: The application is configured as if it is running in the root path of the domain. However, this is not the case here! Our application is located in the
/<repositoryname>/
path, so all links are broken. Of course, this is adjustable β let's try the deployment a second time with the--base-href
option:ng deploy --base-href=/<repositoryname>/ # so in our case it must be ng deploy --base-href=/everything-github-demo/
Hint: Please make sure to really add the trailing slash. It won't work without!
Now you should see our app running on GitHub Pages! You can make sure that everything went well by going into the Settings of the repo. Scroll down there to the GitHub Pages section. The following should appear here:
Hint: You can also see the setting for a custom domain here. Please do not try to set a value. This will work at first, but the setting is not permanent, because
angular-cli-ghpages
will overwrite it again.Now add, commit and push the changes:
git add . git commit -m "build: add angular-cli-gh-pages" git push origin master
5. Automating the Deployment with GitHub Actions
We don't want to stop here and trigger the deployment manually all the time. An ideally workflow would be to make changes to the app and the deployment will then be handled automatically for us. For this we want to use GitHub Actions which is now available for all repositories. So there is no need to register anymore, we can start right now! π
As with all CI/CD systems, we have to think about how to grant the necessary write permissions to our repo.
5.1 About all tokens
The easiest way to grant access to the repository is to implement this with tokens. Tokens can be used instead of a password for Git over HTTPS (this is what we want to do), or can be used to authenticate to the GitHub API (angular-cli-ghpages right now does not use the GitHub API at all).
β οΈ Warning: Treat tokens like passwords and keep them secret. Always use tokens as environment variables instead of hardcoding them into your code!
At GitHub there are several types of tokens, which we have to distinguish carefully. The following two are relevant for us:
GH_TOKEN
/ Personal access token: As the name suggests, personal access tokens grant rights that a particular user has. In order to prevent the release of too many rights, the range of features can be limited with so-called "scopes". Furthermore, scopes can not grant any additional permission beyond those the user already has. Read more about the available scopes here. We will need therepo
access scope, which grants access to all private and public repositories. The use of such a token is dangerous, because it's always possible that potentially someone with bad intentions could gain access to the token. In this case, the evil person would gain more rights than we should actually release β because for the deployment we only need write access to one repo and not all of them. In many projects this token is stored in an environment variable calledGH_TOKEN
or sometimessPERSONAL_TOKEN
.GITHUB_TOKEN
/ Installation access token: GitHub provides this kind of token to authenticate on behalf of GitHub Actions. When you enable GitHub Actions, GitHub automatically installs a GitHub App on your repository, and makes this "installation access token" available as a secret. This is great because there is no need to set up anything extra. Furthermore, the token's permissions are limited to the repository that contains the workflow. The installation access token expires after 60 minutes. GitHub fetches a token for each job, before the job begins. This is pretty awesome, because there are very limited possibilities of what a malicious person could take advantage of. For more information, see "Authenticating with the GITHUB_TOKEN".Git via SSH: As already mentioned, GitHub recommends HTTPS. Of course, we could ignore this advice and save a private key as a secret and use this to authenticate via SSH. However, this key would still be bound to one user and would grant write access to all repositories of the user, too. We do not recommend this option at all, because it's really not very common to maintain public keys on GitHub like one does this with tokens that were at least designed for the purpose of rights management...
In a perfect world the GITHUB_TOKEN
would do all the work for us.
However, for public repositories it doesn't have authorization to create any successive events,
such as GitHub Pages builds.
This means, we can actually push to our gh-pages
branch using the GITHUB_TOKEN
and all the changes would be visible in version control.
But this would NOT trigger a GitHub Pages build so that the website would not change at all (see issue #73 and this comment).
To further confuse the situation, however, for private repositories the token works as desired...
TL;DR
This table gives you an overview:
Token | Private repos | Public repos | Protocol | Setup | Security |
---|---|---|---|---|---|
GITHUB_TOKEN |
β οΈ | βοΈ | HTTPS | Unnecessary | π€© |
GH_TOKEN |
β οΈ | β οΈ | HTTPS | Necessary | π |
(Private SSH Key) | β οΈ | β οΈ | SSH | Necessary | π |
If you are using a private repository, the decision should be clear. For free / public repositories you can use the personal access token and ideally consider the following tip.
Extra tip: If you want to use public repos and therefore use personal access tokens (known as
GH_TOKEN
orPERSONAL_TOKEN
), we recommend to create an additional user account that is only dedicated to be used for deployments.Then this account should only get access to the repositories where there is something to deploy. This increases security again and we have a sufficiently good solution for public repositories.
5.2 Automate a public repo (via GH_TOKEN
)
5.2.1 Setup a Personal Access Token
If we are using a public repository (as described in the previous instructions), then we want to use a personal access token. The procedure is as following:
Create a Personal Access Token with repo access and make sure to copy your new token to the clipboard. You wonβt be able to see it again later! If you want to remember the token later, save it in a secure place only (e.g. a password manager). Please make sure that the token has the following permissions: ![repo access]screenshot_5_repo-access.png)
Open your Angular app's GitHub repo.
Go to Settings > Secrets and click on Add a new secret.
Secrets are encrypted environment variables and only exposed to selected GitHub Actions. GitHub automatically redacts secrets printed to the log, but you should avoid printing secrets to the log intentionally.
Create a secret with name
GH_TOKEN
and paste your token (which you copied in step 1) into the value field. If you prefer, you can also choose the namePERSONAL_TOKEN
for all further steps. Finish this chapter by clicking the green Add secret button.It is perfectly fine not to store the token anywhere else. You can always create new tokens and just throw the old ones away.
5.2.2 Setup the GitHub Action Workflow
Now we have everything ready to create an automated workflow that will do the work for us in the future. GitHub Actions usage is free for public repositories, as mentioned before.
Again in our repo, we go to Actions and click on Set up workflow yourself.
An editor will open. Keep the file name (e.g.
main.yml
) as it is and simply replace the entire content with the following example:name: Deploy to GitHub Pages via angular-cli-ghpages on: [push] jobs: build-and-deploy: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v2 - name: Use Node.js 10.x uses: actions/setup-node@v1 with: node-version: 10.x - name: Prepare and deploy env: GH_TOKEN: ${{ secrets.GH_TOKEN }} run: | npm install npm run lint ### # Configure Angular first! #### # npm test -- --watch=false --progress=false --browsers=ChromeHeadlessCI # npm run webdriver-update-ci # npm run e2e -- --protractor-config=e2e/protractor-ci.conf.js --webdriver-update=false #### npm run ng -- deploy --base-href=/everything-github-demo/ --name="Displayed Username" --email=your.mail@example.org
Fortunately, the step
actions/checkout@v2
checks-out a nice Git repository, where the remoteorigin
is already adjusted. However, username and email are not set. Therefore, we have to include these two values via parameters. Make sure to replace "Displayed Username" (e.g. "I am a bot") and your.mail@example.org with meaningful values in the above example. At this place you do not have to specify any real name and mail. However, you don't want to overdo it, because the provided values will be shown in the Git history later.If you want GitHub Actions to perform unit tests and end-to-end tests, you will need to make some small additional configurations in your Angular app. The necessary changes are described in the Angular CLI Documentation. To be precise:
- You need to update the test configurations for Karma and Protractor
- You have to setup a specific chrome driver version.
Otherwise, the e2e tests will fail with an error message like this:
... E/launcher - session not created: This version of ChromeDriver only supports Chrome version 79
After those changes have been done, you can un-comment sections for the tests in above example.
We can also control when our workflows are triggered: It can be helpful to not have our workflow run on every push to every branch in the repo.
For example, this workflow only runs on push events to
master
andrelease
branches:on: push: branches: - master - release/* # branches matching refs/heads/release/*
Here we see the very common convention of grouping branches with a slash (e.g.
relase/42
). Many Git clients will display those groups like folders.As another example, we can setup a cronjob to run the script every day of the week from Monday β Friday at 02:00 AM:
on: schedule: - cron: 0 2 * * 1-5
For more information see Events that trigger workflows and Workflow syntax for GitHub Actions.
Click on Start commit, add message and description if you like and click on Commit new file. This will add the config file to the repo.
Done! π.
This action will run by itself the first time and each time when you push your changes to GitHub.
5.3 Automate a private repo (via GITHUB_TOKEN
)
The following example is additional,
because we have promised for this article that you do not have to pay a cent.
As already said, the usage of a private repos with GitHub Actions and GitHub Pages is only possible from the pro plan and higher.
However, the big advantage is that we do not have to spend any time and effort to set up a token.
The GITHUB_TOKEN
is already there and we do not need to bother with it at all.
Behind the scenes, GitHub reuses an existing feature known as GitHub apps.
In contrast to programs that authenticate with a "personal access token",
apps do not run on behalf of a specific user β which is great.
To make this happen, GitHub automatically installs a GitHub App on the repository and creates a corresponding "installation access token" with the name GITHUB_TOKEN
available as a secret.
GitHub fetches a new token for each job before the job begins.
Awesome!
βΉοΈ Note
As already mentioned above, the
GITHUB_TOKEN
(installation access token) will only trigger a release of a new website if the action runs in a private repository. In a public repo, a commit is generated, but the site does not change. See this GitHub Community post for more info. If your repo is public, you must still use theGH_TOKEN
(personal access token).
If you have not already done so, please read the text on general workflow usage. The workflow looks very similar, but now we must use the other token. Here is a shorter example, without the tests:
name: Deploy to GitHub Pages via angular-cli-ghpages
on: [push]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Use Node.js 10.x
uses: actions/setup-node@v1
with:
node-version: 10.x
- name: Prepare and deploy
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
npm install
npm run ng -- deploy --base-href=/everything-github-demo/ --name="<Your Git Username>" --email=your.mail@example.org
Et voila! π
This is all it takes to deploy the latest version of the Angular App to GitHub Pages with each push.
6. Extra: Custom Domains
Finally, we would like to shortly describe how you can use your own domain.
GitHub Pages supports custom domains by placing a specific CNAME
file in the root directory of the gh-pages
branch β and angular-cli-ghpages
will create that file for you automatically. GitHub also provides detailed instructions for this.
6.1 Configuring a subdomain
Setting up a subdomain isn't very hard.
And if you already own a domain, you do not need to make any new investments.
In our example the app should be accessible through the domain everything-github-demo.angular.schule
.
First we have to change the DNS for the domain by setting up a so-called
CNAME
record. ThatCNAME
record must point to a domain that looks like this:<username>.github.io
The
<username>
(or organisation name) is in our caseangular-schule
. As an example, the following screenshot shows the required setting for the DNS provider cloudflare.com:Now we have to adjust the
ng deploy
command a little bit! By using our own domain, we no longer have to use a subdirectory. Instead we now specify the parameter--cname
and place the application at the root directory!# OLD ng deploy --base-href=/<repositoryname>/ # NEW ng deploy --cname=everything-github-demo.angular.schule
At the next deployment the website will be known under the new address.
It is important that you have an SSL certificate, because otherwise Chrome will tell your visitors that your website is not secure:
Pretty ugly, isn't it?
The good news: nowadays you don't have to spend money for a SSL certificate anymore! GitHub provides you with a free SSL certificate. To make this possible, they are partnering with letsencrypt.org. You can make the necessary changes in the repository settings. Go to Settings > GitHub Pages and select Enforce HTTPS, to enable HTTPS encryption for your site:It can take up to 24 hours before this option is available. As soon as the change is applied, you can now surf the website via HTTPS:
https://everything-github-demo.angular.schule β please feel free to try it out!Hint: You should not change the custom domain through GitHub's web interface. The next time you deploy the app, this setting will be overwritten. Always use the
--cname
parameter for this.
6.2 Configuring an apex domain
Setting up an apex domain, such as everything-github-demo.com
(this domain does not exist, feel free to be the first π),
is very similar to the procedure for a subdomain.
We need to use the same --cname
parameter as described before, but we have to make some different settings in the DNS.
Navigate to your DNS provider and create either an
ALIAS
anA
record.If your provider already uses the new standard, you should consider using an
ALIAS
record. TheALIAS
record type (also known asANAME
orflattened CNAME
) provides a way to specify a hostname in the DNS records which is then resolved at request time.To create an
ALIAS
record, point your apex domain to a domain that looks like this:<username>.github.io
The
<username>
(or organisation name) is in our caseangular-schule
. As an example, the following screenshot shows the required setting for the DNS provider cloudflare.com:We should also set a
CNAME
entry forwww
, as many people always type in this subdomain. The final settings should look like this:To create an
A
record, point your apex domain to the following IP addresses from GitHub:185.199.108.153 185.199.109.153 185.199.110.153 185.199.111.153
These IP addresses have been the same for years, but you should still compare them against the list in the GitHub documentation.
Summary
We hope you had fun with this tutorial and that your Angular App now builds automatically via GitHub Actions β and runs successfully on GitHub Pages. If you have any questions, please contact us via Twitter. We are also very happy to receive your suggestion for improvement as a PR.
You can check out the complete demo code here:
β https://github.com/angular-schule/everything-github-demo/
Thank you by Dharmen
Special thanks go to Johannes Hoppe for giving me an opportunity to write this article.
Thank you by Johannes Hoppe
Thanks to Dharmen Shah for contributing to angular-cli-ghpages
and for starting this article. I would also like to thank Edric Chan, who has been very involved in both angular-cli-ghpages
and in providing help for this article. π
Only with the help from Dharmen and Edric it was possible for me to finally achieve full support for GitHub Actions in v0.6.2 of angular-cli-ghpages.
Thank you by both
We would like to thank Ferdinand Malcher for reviewing the article and providing critical feedback, and Danny Koppenhagen for kindly proofreading this article.
Suggestions? Feedback? Bugs? Please
About the author
Johannes is one of the two founders of the Angular.Schule and the main developer of angular-cli-ghpages. Recently he has been recognized as a Google Developer Expert (GDE).
About the author
Dharmen is a full stack JavaScript developer from Ahmedabad, India with core skills in JavaScript and CSS. He enjoys working with both Angular and React. Dharmen contributes to some open source projects and has also contributed to the documentation of angular-cli-ghpages. By the way, he is a foodie and loves listening to music, especially Hindi Pop music.