From Zero to Website in 100 Minutes
Set up a Hugo site from scratch using GitHub Codespaces, GitHub Actions and GitHub Pages.
Overview
This workshop will guide you through creating, customizing, and deploying your own website using Hugo and GitHub Pages, all within GitHub Codespaces.
By the end, you will have a fully functional website hosted online, built entirely from your browser.
Prerequisites
- A GitHub account (free)
- No prior web development experience is required
Everything else will be done using GitHub’s cloud tools, so you will not need to install anything locally.
Part 1: Introduction to Static Sites and Hugo
What is a Static Site?
A static website consists of web pages with fixed content. Each page is coded in HTML and displays the same information to every visitor. Think of it like a printed brochure; the content is pre-set and doesn’t change based on who is viewing it.
When a user visits a static site, the server simply sends the pre-made HTML, CSS, and JavaScript files directly to their browser. Conceptually similar to viewing a PDF inside the browser. There is no backend or server-side processing happening.
Thanks to this simplicity, static sites are:
- β‘ Fast: pages are pre-generated and load instantly.
- π Secure: there is no backend code that can be exploited.
- π° Cheap/Free: they work perfectly with free hosting options like GitHub Pages or Netlify.
What is a Dynamic Site?
A dynamic website, on the other hand, can display different content and provide user interaction. The content is generated in real-time based on factors like the user’s location, the time of day, or their past actions on the site.
These websites use server-side programming languages (like PHP, Python, or Ruby) and a database. When a user requests a page, the server processes the request, pulls information from the database, and builds a custom HTML page to send to the user’s browser. This allows for features like user logins, e-commerce, and personalized content.
The upsides of this complexity are:
- π₯ User Interactivity: They allow for interactive features like user accounts, comments, and forums
- βοΈ Easy Content Management: Content can be easily updated by non-technical users through a web application called content management system (CMS).
- π€ Personalized User Experience: They can deliver personalized content to each user based on their preferences and past behavior.
Key Differences
Here’s a breakdown of the main differences between static and dynamic websites:
Feature | Static Website | Dynamic Website |
---|---|---|
Content | Fixed and the same for all users. | Changes based on user interaction and other factors. |
Technology | HTML, CSS, JavaScript. | Server-side languages (PHP, Python, etc.) and a database. |
Performance | Generally faster and more efficient. | Can be slower due to server-side processing and database queries. |
Security | More secure as there is no database or backend to attack. | More vulnerable to attacks due to the database and server-side code. |
Flexibility | Less flexible; content updates require editing the code directly. | Highly flexible; content can be updated through a content management system (CMS). |
Use Cases | Portfolios, blogs, documentation, brochure websites. | E-commerce stores, social media platforms, online forums, membership sites. |
What is a Static Site Generator?
A Static Site Generator (SSG) is a tool that automates the process of creating static HTML files from templates and content files (usually written in Markdown). You write your posts and pages in plain text, and the SSG combines them with your chosen theme to generate a complete website.
This is powerful because we can write content in easy, human-readable formats and we can easily swap out themes for our website without touching the content.
Some popular SSGs include:
- Hugo (built with Go)
- Jekyll (built with Ruby)
- Eleventy (built with JavaScript)
Hugo is one of the fastest and easiest static site generators available. It has several advantages over other alternatives:
- It is distributed as a single binary file, so you do not need to install extra dependencies like Node.js or Ruby.
- It is extremely fast. Even large sites can rebuild in seconds.
- It has a rich ecosystem of themes that make your site look professional immediately.
Part 2: Build Your Hugo Site in Codespaces
In this section, you will create a new Hugo site, set it up in a GitHub repository, and run it locally using GitHub Codespaces.
Step 1: Create a New GitHub Repository
- Go to github.com and sign in
- Click the "+" icon in the top right β “New repository”
- Fill in the details:
- Repository name:
my-hugo-site
(or your choice) - Description: “My personal website built with Hugo” (optional)
- Public (required for free GitHub Pages)
- β Add a README file
- Repository name:
- Click “Create repository”
This repository will not only contain all of our website files, but also our Hugo theme and our development environment configuration. This way we can easily add changes to our website from anywhere with a browser available!
Step 2: Configure GitHub Codespaces
The GitHub platform has a handy feature called “GitHub Codespaces”. They basically give you a complete development environment in the cloud, making use of a technology called devcontainers.
A devcontainer, or development container, is a running Docker container with a well-defined tool and runtime stack that is used as a full-featured, isolated development environment.
If you haven’t heard of Docker or containers yet, then you can conceptually think of this as being similar to having a disposable and easy to reproduce virtual machine you connect to for your development.
Functionally speaking, it is like having Visual Studio Code running inside your browser in a preconfigured environment.
Speaking of “preconfigured environment”, we need to tell Codespaces how to set up our development environment. To do that, we need to create a .devcontainer.json
file.
This file is a configuration file that tells your editor, or in this case, Codespaces, how to create and configure this environment, specifying details like the container image to use, which extensions to install, and what ports to forward.
- In your repository, click “Add file” β “Create new file”
- Name it:
.devcontainer/devcontainer.json
- Paste this configuration:
1 2 3 4 5 6 7 8 9 10
{ "name": "Hugo Development Environment", "image": "mcr.microsoft.com/devcontainers/base:ubuntu", "features": { "ghcr.io/devcontainers/features/hugo:1": { "extended": true, "version": "latest" } } }
- Click “Commit changes” β “Commit directly to the main branch”
Here we have configured an Ubuntu based devcontainer image and then we installed the latest version of hugo-extended using the official hugo feature. This basically sets up the Hugo program in our container for us.
Step 3: Open in GitHub Codespaces
- Click the green “Code” button
- Select the “Codespaces” tab
- Click “Create codespace on main”
Wait 1-2 minutes while Codespaces:
- Creates your container
- Installs Hugo and tools
- Sets up VS Code in your browser
You’ll see a VS Code interface when it’s ready!
Let’s verify our setup. In the terminal at the bottom of Codespaces, run:
|
|
You should see something like: hugo v0.148.0
Step 4: Initialize Your Hugo Site
Running hugo new site
scaffolds a new project with a standard directory structure. Since we already have a README and the devcontainer config, we will also need to pass the --force
flag which allows Hugo to initialize in a non-empty directory:
|
|
Here’s what each of the generated files and folders is for:
archetypes/
: This folder contains templates for your content;
When you create a new piece of content using thehugo new
command (e.g.,hugo new posts/my-first-post.md
), Hugo uses the corresponding archetype file (e.g.,archetypes/posts.md
) to pre-populate the new file with default front matter (metadata like title, date, etc.).assets/
: This is where you place files that need to be processed by Hugo’s asset pipeline, called Hugo Pipes;
This is typically used for things like SASS files that need to be compiled into CSS, or for bundling and minifying JavaScript files.content/
: This is the folder for your site’s content,
You’ll create your pages, blog posts, and other content here, usually as Markdown (.md
) files here. The folder structure insidecontent/
directly maps to the URL structure of your website.data/
: This folder holds structured data files (in TOML, YAML, or JSON format) that you want to use in your templates,
This is useful for things like a list of products, team members, or any other data that isn’t part of a standard page.i18n/
: This folder is for internationalization or creating a multilingual site,
It holds translation files (en.toml
,fr.toml
, etc.) where you can define strings that can be used in your templates to display text in different languages.layouts/
: This folder contains the HTML templates that Hugo uses to render your content into a final website,
You can override templates from your theme here or create custom layouts for different content types. It’s the core of your site’s structure and design.static/
: This folder is for all static assets that don’t need any processing,
Files here (like images, CSS files, JavaScript files, fonts, and PDFs) are copied directly to the final website’s root directory exactly as they are.themes/
: This is where you can install themes created by others to quickly style your site,
A theme is essentially a bundle or package ofassets/
andlayouts/
which customize how your final site will look.hugo.toml
: This is the main configuration file for your entire site. It contains global settings like your website’stitle
,baseURL
, language settings, menus, and any other site-wide parameters.
For a simple new website, you can safely ignore most of these generated folders because they are meant for more advanced customization. Since we are using a pre-built theme, it will provide all the necessary templates, making the layouts/
folder unnecessary at the start. Folders like static
, i18n
, data
, and assets
are also not immediately needed as they handle specific features like custom files, multilingual support, or asset processing that our basic site doesn’t require.
We can clean up the folders we don’t need and make sure to keep those that we do by running:
|
|
We should be left only with the files we care about:
|
|
Step 5: Test our Hugo Site
Running the hugo build
command tells the generator to take all your content files, process them using the templates from your layouts directory (or your theme’s layouts), and build the complete, ready-to-deploy static website.
|
|
By default, it places all the final HTML, CSS, and JavaScript files into a new folder named public/
:
|
|
While this is great, we don’t want to push neither the public/
directory nor the lock-file to our repository. To make sure we don’t accidentally do that, we need to add them to the .gitignore
file.
A .gitignore
file is a plain text file that tells Git which files or directories it should intentionally ignore and not track. This is essential for keeping your repository clean by preventing automatically generated folders, like the public/ directory, from being committed.
Create new file:
.gitignore
Add this content:
1 2 3 4 5 6 7 8 9
### Hugo ### # Generated files by hugo /public/ /resources/_gen/ /assets/jsconfig.json hugo_stats.json # Temporary lock file while building /.hugo_build.lock
At this point, we have the base for our new Hugo site, so let’s go ahead and save our work by commiting it to the main branch:
|
|
However, since our site has no content and no theme configured yet, running hugo now will simply create an empty public/ directory. Before actually building our site, we’ll need to add a theme.
Step 6: Add a Theme
A Hugo theme is a complete, self-contained package of templates, styles, and assets that dictates the design and layout of your website. Instead of you having to create all the HTML, CSS, and JavaScript from scratch in your project’s layouts and static folders, a theme provides a ready-made professional design. You simply add your content, and the theme handles how it’s presented.
We’ll use the PaperMod theme for this workshop.
Adding a Hugo theme as a git submodule is the recommended way to use themes in Hugo because it keeps your project clean and makes it incredibly easy to update the theme later.
A Git submodule is a feature of Git that allows you to keep a Git repository as a subdirectory within another Git repository. This is a powerful way to manage a project that depends on another project. Instead of copying the theme’s code into your project (which would make it hard to get updates), a submodule simply points to a specific commit in the theme’s repository, keeping it separate but connected.
|
|
This should now generate 2 new files for us:
|
|
The .gitmodules
file keeps track of all of the submodules used in this project. If we take a look at it, it basically tells git to put the contents of the given url at the given path:
|
|
With the theme added as a submodule, we need to update our hugo config as well to tell it to use it:
|
|
At this point, everything should be configured so let’s go ahead and commit these changes:
|
|
Step 7: Configure Your Site
Time to configure Hugo! When we added the new theme, new settings became available to configure it. Personally, I am not a huge fan of toml
for configuration files, so we’ll replace hugo.toml
with hugo.yaml
Delete the existing
hugo.toml
fileCreate a new file called
hugo.yaml
Add this configuration (customize with your info):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
baseURL: "https://YOUR-GITHUB-USERNAME.github.io/YOUR-GITHUB-REPO-NAME/" languageCode: "en-us" title: "Your Name" theme: "PaperMod" params: socialIcons: - name: GitHub url: https://github.com/username - name: YouTube url: https://www.youtube.com/@username - name: LinkedIn url: https://linkedin.com/in/username profileMode: enabled: true title: Your Name subtitle: Welcome to my blog! imageUrl: https://...
This hugo.yaml
file controls the main settings for both the Hugo engine and your specific theme (PaperMod). It’s broken into two main sections:
- global settings at the top,
- theme-specific settings under the
params
key.
Global Hugo Configuration
These are the standard settings that Hugo itself uses to build your site.
baseURL
: This is the most important setting. It’s the final web address where your site will live (e.g.,https://my-username.github.io/my-blog/
),languageCode
: This sets the default language of your site, which is important for search engines and user accessibility,title
: This is the main title for your entire website. It will typically appear in the browser tab and in search engine results,theme
: This line simply tells Hugo which theme to use from yourthemes/
folder. In this case, it’s activating “PaperMod”,
Theme-Specific Parameters (params
)
Everything under the params:
key is a custom setting that is specifically read and used by the PaperMod theme. Other themes might have completely different options here, so it is always recommended to check out the official documentation for the specific theme you are using.
In our case, we will be using:
socialIcons
: This will create a list of social media icons and links on our homepage- The
name:
is used by the PaperMod theme to automatically use the correct icon, - The
url:
tell it where to redirect to when the icon is clicked on
- The
profileMode
:enabled: true
: This turns the feature on.title:
andsubtitle:
: These are the lines of text that will appear in the profile box.imageUrl
: This is the direct link to the profile picture you want to display.
With the site customized, let’s go ahead and save our work by commiting it to the main
branch:
|
|
Step 8: Test Your Site
In Codespaces, we need to use a special command to make the site work correctly with forwarded ports:
|
|
Let’s break down each piece of the command:
hugo server
: This is the standard command to start Hugo’s built-in development server
It watches for any changes you make to your files and automatically rebuilds your site in memory, so you can see your changes live in the browser.-D
: This is a shorthand flag for--buildDrafts
,
It tells the server to include any content you’ve marked as a draft (by addingdraft: true
to the top of a Markdown file). This is very useful during development, as it allows you to preview your work-in-progress posts without them being included in the final production build.--baseURL https://...
: This is the most critical part for Codespaces. It temporarily overrides thebaseURL
from yourhugo.yaml
file.- Why? Your Codespace is a remote machine in the cloud, and to show you the website running on it, GitHub automatically “forwards” the server’s port (1313 by default for Hugo) to a special, public URL.
$CODESPACE_NAME
and$GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN
are environment variables that Codespaces provides. They automatically insert your unique codespace name and the correct forwarding domain to construct the exact URL you need to access your live preview.
--appendPort=false
: This flag tells hugo NOT to append the port to the generated url
By default, Hugo adds the port number (like:1313
) to the end of thebaseURL
. However, the special Codespaces URL already has the port1313
encoded within it (e.g.,your-codespace-name-**1313**.preview.app.github.dev
). This flag tells Hugo not to add the port a second time, which would break the link.
You can press Ctrl+C
to stop the server.
To simplify the development command, create a file called Makefile
and add:
|
|
Important: The indentation before hugo server
MUST be a TAB, not spaces!
You can now start the local server by simply running:
|
|
Let’s commit this new file to git:
|
|
Step 9: Create Your First Blog Post
Create a new post using:
|
|
This creates a new file at content/posts/my-first-post.md
. Open it and edit:
|
|
While we did add a new blog post, if we check out our site using make dev
, we won’t be able to find it anywhere. We need to configure hugo a bit to add some navigation to our blog page:
|
|
With the blog post added and Hugo configured, I think our brand new site is done! Let’s save our work one last time:
|
|
Congratulations! Your site is built! Now let’s deploy it. π
Part 3: CI/CD, GitHub Actions, and GitHub Pages
Now that your site is working “locally” (quotes because we are actually working in the cloud), the next step is to publish it to the web so that anyone can visit it.
The Codespaces preview URL we used earlier runs on a temporary development server. When you close your Codespace, that link disappears. To make your site available permanently, we will use GitHub Pages.
Instead of building the site manually each time you make a change, you can automate the entire process using CI/CD via GitHub Actions. This ensures your site stays up to date automatically whenever you push new content to your repository.
But first…
What is CI/CD?
CI/CD stands for Continuous Integration and Continuous Deployment.
- Continuous Integration (CI) means automatically building and testing your code whenever you make changes.
- Continuous Deployment (CD) means automatically publishing those changes to your live website when the build succeeds.
In our case, CI will run hugo build
to build the site in the public/
directory, and CD will take the public/
directory and deploy it to GitHub Pages.
While we could technically do all these CI and CD steps manually, foing this automatically instead has quite a few benefits, the most important ones being: That’s an excellent question. You absolutely can manually run hugo build and push the generated files to GitHub Pages, but automating this process with CI/CD offers some powerful advantages that become more valuable over time.
- β‘Simplicity and Speed: With an automated workflow, your only job is to write content and push your changes to the main branch. The CI/CD pipeline takes care of everything else in the background. This saves you from having to run multiple commands every time you want to publish a simple typo fix or a new blog post.
- π¦ Consistency and Reliability: Humans make mistakes. You might forget to run the
hugo build
command, or you might accidentally delete a file when moving thepublic/
contents. An automated process is a robot that follows the exact same perfect steps every single time, eliminating the risk of human error and ensuring your site always deploys correctly.
What Are GitHub Actions?
GitHub Actions is GitHub’s native way of implementing the CI/CD (Continuous Integration/Continuous Deployment) we just discussed.
Think of it as a programmable robot that lives inside your repository and can perform tasks for you automatically whenever something happens, like you pushing new code.
We will use GitHub Actions to build and deploy our Hugo site whenever we push changes to the main branch.
What is GitHub Pages?
GitHub Pages is a free static web hosting service offered directly by GitHub.
In simple terms, it’s a feature that takes the HTML, CSS, and JavaScript files from a repository in your GitHub account and publishes them as a live website that anyone can visit on the internet.
It’s the final piece of the puzzle and serves the static files we’ve been talking about.
The URL for a project site typically follows a standard format: https://your-username.github.io/your-repository-name
, which is exactly why your Hugo baseURL
is configured that way.
Here is what will happen once everything is set up:
- You create or edit content in your Hugo site and push the changes to GitHub.
- GitHub Actions automatically runs a workflow that builds your site with Hugo, generating the final static files.
- GitHub Pages takes those generated files and publishes them online at a permanent public URL.
Part 4: Deploy to GitHub Pages with GitHub Actions
The final step before your site goes live is to set up a GitHub Actions workflow that automatically builds and deploys your Hugo site whenever you push updates to your repository.
Step 10: Configure GitHub Actions Workflow
GitHub Actions uses a configuration file written in YAML (a structured text format) that describes what should happen and when.
Weβll place this file in the .github/workflows/
directory so that GitHub recognizes it as an automated workflow, and we’ll call it something suggestive like gh-pages.yaml
|
|
Understanding the Workflow
Letβs break this file down section by section so we understand what it does.
1. name: GitHub Pages
This simply gives a name to our workflow. It helps us identify it later in the Actions tab on GitHub. This is especially handy if we have a lot of workflows to automate various things in our projects.
2. permissions
This is security section which controls what the GitHub Actions bot is allowed to do in your repository.
contents: read
lets it read our code,pages: write
gives it permission to upload files to GitHub Pages,id-token: write
allows the workflow to authenticate securely with GitHub Pages when deploying.
3. on
This section defines when the workflow should run.
push: branches: ["main"]
means it will run automatically every time you push a commit to yourmain
branch.workflow_dispatch
allows you to start the workflow manually from the GitHub Actions interface. You can navigate to the Actions tag, select your workflow on the left and then using the drop-down menu on the right, trigger a new run.
4. jobs
Each workflow is made up of one or more jobs. Jobs describe what should be done and on what kind of system.
In this case, we have two jobs:
build
β generates the site filesdeploy
β publishes those files to GitHub Pages
The build
Job
The first job handles the process of building our Hugo site.
runs-on: ubuntu-latest
tells GitHub to run this job on a Linux virtual machine.concurrency
ensures that only one build runs per branch at a time, preventing overlapping builds.
Inside steps
, we list the individual actions that make up the build process:
- Checkout the repositoryThis pulls down our code and theme (since Hugo themes are stored as Git submodules).
1 2 3
- uses: actions/checkout@v5 with: submodules: true
- Set up HugoThis installs the correct version of Hugo on the runner, similar to how we had it preinstalled in our Codespace.
1 2
- name: Setup Hugo uses: peaceiris/actions-hugo@v3
- Build the siteThis runs the Hugo build command, generating our static site into the
1 2
- name: Build run: hugo build --minify
public/
directory. The--minify
flag compresses files to make them load faster. - Save the build outputThis packages the generated files as an artifact (a saved bundle) that can be passed to the next job.
1 2
- name: Save artifact uses: actions/upload-pages-artifact@v3
The deploy
Job
The second job is responsible for actually publishing the site.
needs: build
means this job will only start after thebuild
job has finished successfully.environment: github-pages
tells GitHub that this deployment targets GitHub Pages.- The
url
line makes the final site URL available as an output variable.
Inside the steps:
|
|
This step uses GitHubβs official deployment action to take the built files (the artifact created earlier) and publish them to your repositoryβs GitHub Pages environment.
After You Push
Once this file is added, commit and push it to your repositoryβs main
branch.
|
|
Then go to the Actions tab on GitHub to watch the workflow in real time.
- The build job will install Hugo, build the site, and upload the files.
- The deploy job will take those files and push them to GitHub Pages.
Once the workflow shows a green checkmark, go back to “Settings” β “Pages” and you should see: “Your site is live at https://YOUR-USERNAME.github.io/my-hugo-site/"
π Congratulations! Your site is live on the internet!
But wait, something is wrong… Your blog post is not showing up! Let’s fix that.
Bonus: Push Updates to the Site
Make a change to test automatic deployment:
- Go back to the repository page
- Edit
content/posts/my-first-post.md
-> changedraft: true
todraft: false
- Commit and push:
1 2 3
git add . git commit -m "update post" git push origin main
- Go to the Actions tab on GitHub
- Watch the workflow run automatically
- Once complete, refresh your live site.
This is the magic of CI/CD! Every push automatically deploys your site.
Conclusion
Congratulations! You have built and deployed your first static website.
You learned:
- What static and dynamic sites are
- What a static site generator does
- How to use Hugo to build a website
- How to develop in the cloud with GitHub Codespaces
- How to automate builds and deployments with GitHub Actions
- How to host your site for free with GitHub Pages
You now have a live, professional-looking website built entirely with modern developer tools. From here, you can customize your theme, add new pages, and continue publishing content with ease.
Useful Resources
DevContainers: DevContainers Beginner Guide | Docker in 100 seconds | Docker in 5 minutes
Hugo: Official Documentation | Hugo Forum | Hugo Themes | Hugo in 100 seconds | Full Hugo Tutorial Playlist
PaperMod Theme: Documentation | Features | FAQs
GitHub: GitHub Actions Docs | GitHub Pages Docs | Codespaces Docs