# January 9th, 2020
# Continuous Deployment with CircleCI and s3deploy
Now I have a simple to edit, static site that meets my résumé hosting and blogging needs; but deployment is currently a bit tedious. Manually copying the built files into the S3 bucket and invalidating the CloudFront distribution takes too much time and requires too much fiddling with my mouse to encourage consistent updates. 😉
So, what am I after? I think a desirable workflow looks something like:
yarn docs:dev
- Write a blog post
- Commit and push the repository to GitHub
yarn docs:build
is run automatically- The output (
docs/.vuepress/dist/
) of the build is uploaded to AWS S3 - The CloudFront distribution in front of the S3 bucket is invalidated
Steps 1, 2, and 3 are already doable; so what about 4, 5, and 6?
Sounds like a job for CircleCI! CircleCI integrates with our existing GitHub repository to provide a configurable build / deploy pipeline as a service. It also offers a free plan which will work for us in this case.
In order to perform the AWS operations of syncing the bucket and invalidating
the distribution, I've chosen to use the often recommended
bep/s3deploy script. It's kind of like aws s3 sync
, but optimized for static sites to modify only the minimal set of
files.
Setting up CircleCI is as simple as making an account and using their interface
to set up a new project tracking our GitHub repository. Once that's complete, we
will need to create .circleci/config.yml
in the root of the repository; but
before we get to editing it we need to satisfy a prerequisite first: creating a
deployment service account.
# AWS IAM User
First we need to create an IAM User in AWS that will represent the service account under which s3deploy will do its work.
Add a user with the "Programmatic access" type. Click "Next: Permissions", select "Attach existing policies directly", and then click on the "Create policy" button.
The policy that I'm using looks like:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:ListBucket",
"s3:GetBucketLocation"
],
"Resource": "arn:aws:s3:::www.ethanaa.com"
},
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:DeleteObject",
"s3:PutObjectAcl"
],
"Resource": "arn:aws:s3:::www.ethanaa.com/*"
},
{
"Effect": "Allow",
"Action": [
"cloudfront:GetDistribution",
"cloudfront:CreateInvalidation"
],
"Resource": "*"
}
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
It allows s3deploy to fetch the information that it needs about the state of the S3 bucket, insert / update / delete bucket contents, and invalidate our CloudFront distribution (CDN cache) so that our changes are immediately available.
Make sure to download / copy the credentials for the new user to a file. We'll
need both the AWS_ACCESS_KEY_ID
and the AWS_SECRET_ACCESS_KEY
for the
configuration of our CircleCI project's build environment.
# CircleCI
Set both AWS_ACCESS_KEY_ID
and AWS_SECRET_ACCESS_KEY
to the values
associated with the newly created deployment service account IAM User.
Now edit .circleci/config.yml
:
# Javascript Node CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-javascript/ for more details
#
version: 2.1
defaults: &defaults
working_directory: ~/project
docker:
- image: circleci/node:latest
jobs:
#------------------------------------------------------------
# 1. Install dependencies
#------------------------------------------------------------
install-dependencies:
<<: *defaults
steps:
- checkout
- restore_cache:
keys:
- v1-deps-{{ checksum "yarn.lock" }}
- v1-deps
- run:
name: "Install yarn dependencies"
command: yarn --frozen-lockfile --non-interactive
- run:
name: "Install s3deploy"
command: curl -L https://github.com/bep/s3deploy/releases/download/v2.3.2/s3deploy_2.3.2_Linux-64bit.tar.gz | tar xvz
- save_cache:
key: v1-deps-{{ checksum "yarn.lock" }}
paths:
- ~/.cache/yarn
- persist_to_workspace:
root: ~/project
paths:
- node_modules
- s3deploy
#------------------------------------------------------------
# 2. Build VuePress
#------------------------------------------------------------
build:
<<: *defaults
steps:
- checkout
- attach_workspace:
at: ~/project
- run:
name: "Run build"
command: yarn docs:build
- run:
name: "Copy resume pdf into dist"
command: cp Ethan_Anderson_Resume.pdf docs/.vuepress/dist/
- persist_to_workspace:
root: ~/project
paths:
- docs/.vuepress/dist
#------------------------------------------------------------
# 3. Deploy to S3
#------------------------------------------------------------
deploy:
<<: *defaults
steps:
- attach_workspace:
at: ~/project
- run:
name: "Deploy to S3"
command: |
if [ "${CIRCLE_BRANCH}" = "master" ]; then
cd ~/project/docs/.vuepress/dist
~/project/s3deploy -region=us-east-1 -bucket=www.ethanaa.com -distribution-id=E272FZWDOFIWKK -v
else
echo "Non-master branch: dry run only"
echo
echo "Working directory files:"
pwd
echo
ls -lAF
cd ~/project/docs/.vuepress/dist
echo "Built files:"
pwd
echo
ls -lAF
echo
~/project/s3deploy -region=us-east-1 -bucket=www.ethanaa.com -distribution-id=E272FZWDOFIWKK -v -try
fi
#------------------------------------------------------------
# Workflows
#------------------------------------------------------------
workflows:
version: 2
build:
jobs:
- install-dependencies
- build: {requires: [install-dependencies]}
- deploy: {requires: [build]}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
I've separated the config into 3 jobs: install-dependencies
, build
, and deploy
.
First the dependencies are installed by running yarn
as well as downloading
and extracting the latest release of s3deploy. Then yarn docs:build
is run and
my résumé is copied from the root directory into docs/.vuepress/dist/
so that
it's processed by s3deploy. Finally, s3deploy is executed targeting our S3
bucket and CloudFormation distribution.
If you push to any other branch than master then s3deploy will be run with
-try
; resulting in a dry-run execution that will not modify any AWS objects.
So, let's commit the code and push it up to GitHub to trigger a build!