This tutorial will walk you through how to setup and deploy Hugo blog in AWS using all features like HTTP/2, HTTPS only access, CDN and many others.
At the end you will have:
Before to jump into steps a couple word about Hugo. Hugo is a static website generator. That’s one of quite a big number of generator nowadays (see for example a post on Medium with an overview actual for 2019). What makes static website generators different from the traditional CMS platforms like WorkPress or Drupal is that you typically need only static hosting (read no Application Server) to keep your website running.
If you haven’t purchased domain it’s the time look at NameCheap or GoDaddy. From here and below assume the blog will be hosted at example.com
domain, so please replace it down the road with your domain name.
Install Hugo(see Quick Start).
Choose the theme (for example, I’ve selected the current using filter all themes with a ‘blog’ tag)
Now create a new directory for the blog:
$ hugo new site example
Congratulations! Your new Hugo site is created in /Users/user/Documents/hugo/example.
...
It makes sense to initialize git repository even before any website is published (make sure git is installed and configured). This is also required if you want to use git submodule tool for themes. To do that type in console in the website root directory:
$ git init
Initialized empty Git repository in /Users/user/Documents/hugo/example/.git/
You’ll probably want to create a .gitignore file right away (in order to ask git to ignore all generated by Hugo file). For this purpose we can use a template from GitHub:
$ wget -O .gitignore https://raw.githubusercontent.com/github/gitignore/master/community/Golang/Hugo.gitignore
--2019-05-09 09:25:52-- https://raw.githubusercontent.com/github/gitignore/master/community/Golang/Hugo.gitignore
Resolving raw.githubusercontent.com... 151.101.200.133
Connecting to raw.githubusercontent.com|151.101.200.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
...
$ git add .gitignore
if you use macOS the you would need add .DS_Store to the just downloaded file:
$ echo ".DS_Store" >> .gitignore
Then download the chosen theme (for instance, I picked this minimal style theme):
$ git submodule add https://github.com/vividvilla/ezhil.git themes/ezhil
Update config.toml
as it suggested on the theme page.
I prefer using Visual Studio Code and I wasn’t surprised to see an extension for Hugo. After its installation there is a nice option to invoke major Hugo commands right from VS Code: Cmd+Shift+P
(called Command Palette) then start typing ‘hugo’.
Create the first blog file calling ‘Hugofy: New Post’ from Command Palette with a name hugo-post.md
(you don’t need to specify posts
folder as it is required for CLI command)
Write a text under the header. If you experience an issue with markdown code blocks in a list just don’t forget to tab the whole block as in example here.
Now, start the Hugo server with drafts enabled:
$ hugo server -D
| EN
+------------------+----+
Pages | 7
Paginator pages | 0
Non-page files | 0
Static files | 4
Processed images | 0
Aliases | 0
Sitemaps | 1
Cleaned | 0
Total in 13 ms
Watching for changes in /Users/user/Documents/hugo/example/{content,data,layouts,static,themes}
Watching for config changes in /Users/user/Documents/hugo/example/config.toml
Environment: "development"
Serving pages from memory
Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender
Web Server is available at http://localhost:1313/ (bind address 127.0.0.1)
Press Ctrl+C to stop
Open browser with URL http://localhost:1313/ as it was suggested above and check the website works.
Now it is time to add files to git repository:
$ git status
On branch master
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: .gitmodules
new file: themes/ezhil
Untracked files:
(use "git add <file>..." to include in what will be committed)
.gitignore
archetypes/
config.toml
content/
$ git add .gitignore archetypes/* config.toml content/*
$ git status
On branch master
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: .gitignore
new file: .gitmodules
new file: archetypes/default.md
new file: config.toml
new file: content/post/hugo-post.md
new file: themes/ezhil
And commit:
$ git commit -m "Initial commit"
[master (root-commit) 4ec9563] Initial commit
6 files changed, 155 insertions(+)
create mode 100644 .gitignore
create mode 100644 .gitmodules
create mode 100644 archetypes/default.md
create mode 100644 config.toml
create mode 100644 content/post/hugo-post.md
create mode 160000 themes/ezhil
Request an SSL certificate. Follow the AWS docs to request a public certificate for your domain. Some important highlights are:
A critical point is from ACM service documentation:
To use an ACM Certificate with CloudFront, you must request or import the certificate in the US East (N. Virginia) region.
i.e., change region to US East N. Virginia if needed (top right corner within the AWS admin console).
Many others guidance cross the Internet usually suggest creation manually S3 and CloudFront, of course it is not a big deal however the current guidance make leverage capabilities of a great tool s3_website tool.
First it should be installed s3_website and configuration files created in the project root directory:
$ gem install s3_website
$ s3_website cfg create
Then update just created s3_website.yaml file to be as presented below:
s3_id: <%= ENV['S3_ID'] %>
s3_secret: <%= ENV['S3_SECRET'] %>
s3_bucket: <%= ENV['S3_BUCKET'] %>
# Below are examples of all the available configurations.
# See README for more detailed info on each of them.
site: public
index_document: index.html
error_document: 404.html
max_age: 120
gzip:
- .html
- .css
- .md
- .cs
- .xml
- .json
# - .md
# gzip_zopfli: true
cache_control:
"css/*": public, max-age=3600
"font/*": public, max-age=3600
"js/*": public, max-age=3600
# See http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region for valid endpoints
s3_endpoint: us-east-1
# ignore_on_server: that_folder_of_stuff_i_dont_keep_locally
exclude_from_upload:
- .DS_Store # delete macOS stuff
# - those_folders_of_stuff
# - i_wouldnt_want_to_upload
s3_reduced_redundancy: true
#cloudfront_distribution_id: <%= ENV['CLOUDFRONT_ID'] %>
cloudfront_distribution_config:
default_cache_behavior:
min_ttl: <%= 60 * 60 * 24 %>
http_version: http2
aliases:
quantity: 1
items:
- example.com
- www.example.com
# cloudfront_invalidate_root: true
cloudfront_wildcard_invalidation: true
# concurrency_level: 5
# redirects:
# index.php: /
# about.php: about.html
# music-files/promo.mp4: http://www.youtube.com/watch?v=dQw4w9WgXcQ
# routing_rules:
# - condition:
# key_prefix_equals: blog/some_path
# redirect:
# host_name: blog.example.com
# replace_key_prefix_with: some_new_path/
# http_redirect_code: 301
What the suggested configuration does:
site
attribute )gzip
attribute)cache_control
)exclude_from_upload
)s3_reduced_redundancy
).min_ttl
)http_version
)The next is to create AIM user with Programmatic access for s3_website, since the aim is to delegate S3 and CloudFront creation to s3_website AIM user should have full access to S3 bucket and CloudFront. A guidance Creating an IAM User in Your AWS Account can be used.
Create .env file in the project directory and fill it with content below and AIM user credentials:
S3_ID=<client_ID>
S3_SECRET=<client_secret>
S3_BUCKET=example.com
CLOUDFRONT_ID=<leave empty for now>
Since the file contains sensitive information it should be also add to .gitignore:
$ echo ".env" >> .gitignore
Now it is time to create magically S3 bucket and CloudFront distribution:
$ s3_website cfg apply
Applying the configurations in s3_website.yml on the AWS services ...
Created bucket example.com in the us-east-1 Region
Bucket example.com now functions as a website
No redirects to configure for example.com bucket
Bucket example.com is now readable to the whole world
Do you want to deliver your website via CloudFront, Amazon’s CDN service? [y/N]
y
The distribution <CF Distribution ID> at <CF prefix ID>.cloudfront.net now delivers the origin help42.blog.s3-website-us-east-1.amazonaws.com
Please allow up to 15 minutes for the distribution to initialize
For more information on the distribution, see https://console.aws.amazon.com/cloudfront
Added setting 'cloudfront_distribution_id: <CF Distribution ID>' into ./s3_website.yml
Applied custom distribution settings:
default_cache_behavior:
min_ttl: 86400
http_version: http2
Open AWS console and update just created CloudFront distribution to redirect from HTTP => HTTPS and use generated above SSL certificate from AWS ACM.
Go back to .env file and replace the placeholder with created CloudFront distribution ID.
Everything ready to publish the blog:
$ s3_website push
[info] Deploying /Users/user/Documents/hugo/example/public/* to example.com
[warn] Overriding the max_age setting with the cache_control setting
[warn] Overriding the max_age setting with the cache_control setting
[warn] Overriding the max_age setting with the cache_control setting
[warn] Overriding the max_age setting with the cache_control setting
[succ] Created index.xml (max-age=120 | application/rss+xml | gzip | REDUCED_REDUNDANCY)
[succ] Created js/main.js (public, max-age=3600 | application/javascript | REDUCED_REDUNDANCY)
[succ] Created sitemap.xml (max-age=120 | application/xml | gzip | REDUCED_REDUNDANCY)
[succ] Created post/index.xml (max-age=120 | application/rss+xml | gzip | REDUCED_REDUNDANCY)
[succ] Created index.html (max-age=120 | text/html; charset=utf-8 | gzip | REDUCED_REDUNDANCY)
[succ] Created tags/hugo/index.html (max-age=120 | text/html; charset=utf-8 | gzip | REDUCED_REDUNDANCY)
[succ] Created tags/index.html (max-age=120 | text/html; charset=utf-8 | gzip | REDUCED_REDUNDANCY)
[succ] Created tags/index.xml (max-age=120 | application/rss+xml | gzip | REDUCED_REDUNDANCY)
[succ] Created post/hugo-post/index.html (max-age=120 | text/html; charset=utf-8 | gzip | REDUCED_REDUNDANCY)
[succ] Created css/normalize.css (public, max-age=3600 | text/css; charset=utf-8 | gzip | REDUCED_REDUNDANCY)
[succ] Created css/main.css (public, max-age=3600 | text/css; charset=utf-8 | gzip | REDUCED_REDUNDANCY)
[succ] Created post/index.html (max-age=120 | text/html; charset=utf-8 | gzip | REDUCED_REDUNDANCY)
[succ] Created tags/hugo/index.xml (max-age=120 | application/rss+xml | gzip | REDUCED_REDUNDANCY)
[succ] Created js/feather.min.js (public, max-age=3600 | application/javascript | REDUCED_REDUNDANCY)
[info] Summary: Created 14 files. Transferred 83.6 kB, 266.0 kB/s.
[info] Successfully pushed the website to http://example.com.s3-website-us-east-1.amazonaws.com
Open the website https://example.blog/ and check if it works.
Commit to git what it is done for now:
$ git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: .gitignore
Untracked files:
(use "git add <file>..." to include in what will be committed)
.vscode/
s3_website.yml
no changes added to commit (use "git add" and/or "git commit -a")
$ git add .vscode/ s3_website.yml
$ git commit -m "Configured s3_website tool"
Go to Google Analytics homepage and setup a new property according to the Help page. After creation you will be automatically redirected to Tracking Info / Tracking Code page. The value Tracking ID
should be copied and used in config.toml
file.
Go to Disqus homepage and configure an instance using a guide: How to install Disqus on Hugo?. Update config.toml
using Shortname
from Configure Disqus for Your Site page.
Go to Google Search Console and register your property. The recommended way to proof ownership is to use DNS validation: just copy required TXT value and go to Route53, create another record set with TXT type and paste the value from Google Search Console. It might take a while (about 5 minutes in my case) to propagate DNS records.
To kick off Google crawler to index the web site go to Sitemaps navigation menu item and type the path to sitemap.xml generated by Hugo: https://example.com/sitemap.xml