Download as pdf or txt
Download as pdf or txt
You are on page 1of 15

Creating a CICD for your Ionic 4 app in AWS in

5 minutes
January 19, 2019
aws, cicd, ionic, s3
Author: Matthew Bonig (@mattbonig)

Ionic 4 just hit RC2 and I am playing around with a simple Task app for a friend. He wants
mobile apps and a
website, so a perfect fit for Ionic, since it can be deployed as a static
website pretty easily.

Combine that with AWS's recent announcement of S3


deployment support for CodePipeline
means we now have a very
simple method for building and deploying the PWA side of the
Ionic app. I intend to show you how you can get it
setup in about 5 minutes**. When all
done, the process will look something like this:

Basically, the CICD for an Ionic PWA

1. Developer pushes code to CodeCommit/Github repository


2. CodeCommit fires a webhook to CodePipeline
3. CodePipeline pulls code from the repository and hands over to CodeBuild
4. CodeBuild compiles Ionic into static files for the web and pushes to S3
5. CloudFront updates cache from S3
6. End users read built files from CloudFront site.

By the end of this blog, you should be able to deploy your own CodeCommit/Github
repository to the public web
entirely by CICD.

We're going to start with a basic Ionic 4 application. I assume you already have the Ionic cli
installed (
npm i -g ionic if not), so let's bootstrap a new project:

$ ionic start myApp tabs --type=angular && cd myApp

You can verify the build for the web app works:

$ ionic build --prod

When done you should now see a bunch of compiled files in the
./www/
directory. These are
Ionic's deployable web assets.

Let's make this a process we can easily run with npm. Update your package.json, adding a
new "build:prod" to
your scripts config:

"scripts": {

"ng": "ng",

"start": "ng serve",

"build": "ng build",

"test": "ng test",

"lint": "ng lint",

"e2e": "ng e2e",

"build:prod": "ionic build --prod"

Now let's add a buildspec.yml file to the root of the project. We're going to point
CodeBuild
at this later.
version: 0.2

phases:

pre_build:

commands:

- npm i

build:

commands:

- npm run build:prod

artifacts:
files:

- '**/*'

base-directory: www

That's all we need to do to the code, so now push this to your repo, either AWS CodeCommit
or Github.

Now login to your AWS CodePipeline


Console. We're going to create a new CodePipeline:

Start creating the CodePipeline


Fill it out similar to above. You can override as you see fit but the defaults should work. Click
Next.

Pick your source

Fill out the source, in this case I'm using a Github repository. Click Next. Now we
setup the
Build State, the CodeBuild step:

Do the build

Click Create Project. In the new window start to fill out some basic CodeBuild info:
CodeBuild project details

Then fill out the specifics for the build:

Ionic is Node-based, hence using a Node Runtime. Your mileage may vary.

We'll be using a Node-based container for our build environment. You can leave everything
else on the
defaults:
Defaults are good** here.

**If you want to log the build output - which I suggest you do but is not required - go ahead
and fill out
CloudWatch specifics (the correct CloudWatch policies will NOT automatically
be added to the related service
role. You'll need to add those yourself).

Remember to add the policies later

When you're all done, click Continue to CodePipeline. Your CodeBuild project should now
be
selected.

Confirm your newly created CodeBuild project


The next step you will select an S3 bucket to deploy your files to. However, that screen
doesn't give you the
option to create the bucket the same way we got to create a CodeBuild
project on the fly. So go ahead and open a
new tab for the AWS S3 Console and
create a
bucket over there.

Bucket time!

You can accept all defaults if you want, the bucket does not need to be, and shouldn't be,
public.

Review and Create the bucket. Now go back to your other tab, where you have setup the
CodeBuild project in CodePipeline and hit Next.

Pick your bucket


Pick your bucket and make sure you pick to 'Extract file before deploy'. Hit Next.

Review all your settings one last time and then hit Create Pipeline:

Review and Create the CodePipeline!

Once created, CodePipeline will automatically run the first build. No need to push anything
to the
repository.

Now, if you picked to log your CodeBuild project but didn't remember to setup the
permissions manually, or didn't
get to it quick enough, you're going to see an error:

ACCESS_DENIED: Service role arn:aws:iam::XXXXXX:role/service-ro

Even though AWS setup the service role for you, it did not give it permissions to write
CloudWatch logs. Read here if you are unsure how to do this. For my testing, I just attached
the
CloudWatchAgentServerPolicy
to my service role.

You should see a completed CodePipeline like this:


First run of the build!

And if you check your S3 bucket, you should see files similar to this:

Look at those glorious files!

Great! We're almost done. Now we just need a CloudFront distribution in front of this. Now, I
know what you're
wondering:

"But can't I just use S3 Static Website Hosting" - me, at the start of this
You'd think, and if you didn't care about SSL, you probably could. However, any good website
should have SSL and
CloudFront also solves a few issues around routing for us, so let's
continue...

Start creating a new Web distribution in the CloudFront Console:

Pick your bucket, and setup access

Pick your S3 bucket as the origin domain name. We want to restrict bucket access, so that
we don't have to make
our S3 bucket public. Let CloudFront take care of granting the
permissions in the bucket policy. The only other
default you should have to change is the
Default Root Object near the bottom, set that
to "index.html".

I'm not going to setup a CNAME alternative for this (for custom domain names) but you
probably will want to, so
read up on it here. When you're satisfied, go ahead and create the
distribution.

It'll take a few minutes, but once complete you should be able to load the site at the Domain
Name
CloudFront generated for your distribution.

And that's generally it. You should now have a functioning website based on the Ionic
starter template. Any
updates you push to your repository will be deployed out after a few
minutes of the CICD process running.
However, since we're using CloudFront, the cache will
still show the old files. That's not good. You COULD let
the cache naturally expire, or
invalidate it yourself, but one requires waiting FAR too long to see the new
code, and the
other is manual. Both are gross.

We need a way to tell CloudFront to invalidate the files after they've been redeployed to S3.
So, let's take a
look at our options. If we go into editing the CodePipeline, let's take a look at
what kind of actions we can
take after the S3 push:
I don't see anything that says CloudFront

Hmm, so nothing regarding CloudFront, bummer. What else we got?

Lambda to the rescue!

Ah ha, Lambda! The Swiss Army Knife of AWS. Since we can call a Lambda we can easily
create one that will
invalidate the CloudFront cache:

const AWS = require('aws-sdk');

const cloudfront = new AWS.CloudFront();

exports.handler = async(event) => {

// Extract the Job ID

console.log('event:', event);

const job_id = event['CodePipeline.job']['id'];

// Extract the Job Data

const job_data = event['CodePipeline.job']['data'];

console.log('job_data:', job_data);

const distribution_id = job_data.actionConfiguration.config


console.log('invalidating distribution:', distribution_id);
await cloudfront.createInvalidation({

DistributionId: distribution_id,

InvalidationBatch: {

CallerReference: `invalidate-after-s3-${new Date().


Paths: {

Quantity: 1,

Items: ['/*']

}).promise();

var codepipeline = new AWS.CodePipeline();

await codepipeline.putJobSuccessResult({

jobId: job_id

}).promise();

return {

statusCode: 200,

body: ''

};

};

I'm not going to cover all the steps in creating this Lambda. Just don't forget to give it an
execution role
that Allows the cloudfront:CreateInvalidation and
codepipeline:PutJobSuccessResult
Actions. Let's go ahead and
setup a new Action Group on
the Deploy stage of our CodePipeline and add the Lambda:
Add a new action group below the S3 deploy

For the UserParameter, pass the CloudFront Distribution ID you find over in the
CloudFront
console.

Add the invalidate action

Alright, now if you Release the CodePipeline, you should see the S3 files get deployed
and
then our CloudFront
cache is invalidated.
Deploy + Invalidate

You should see a related record in the CloudFront Console:

Invalidated!

Great! Now we have this fully deployable application when we do a git push to 'master'.
Try
it out now, change something significant (and obvious) in your Ionic project and then push
to your
repository.
CICD!

As you can imagine, this process would work with any static website, since nothing we did
here was specific to
Ionic 4, other than our buildspec.yml file and some config when we
setup the CodeBuild
project.

**Now, as you probably noticed, this was likely a little more than 5 minutes. I think if you
knew the process and
weren't following the guide, 5 minutes could be enough time. So, yes,
"5 minutes" was clickbait in my title. Sue
me. But, since that's still too long and I now have
guilt, I created a Terraform module
you can use to create all of this in just a minute. Check it
out here:

https://github.com/mbonig/simple-aws-cicd

Check me out on Twitter @mattbonig

You might also like