DEV Community

Cover image for ๐Ÿš€ Create Professional Portfolios Using Material for MkDocs (Series-X)
Mr Chike
Mr Chike

Posted on • Edited on

๐Ÿš€ Create Professional Portfolios Using Material for MkDocs (Series-X)

๐Ÿ“Œ See the source code on GitHub: Material for MkDocs Portfolio Template
๐Ÿ”ฅ Live Demo: Portfolio Site

This article teaches you how to create stunning, professional portfolios using Material for MkDocs, a fast, customizable, and open-source static site generator trusted by over 20,000 users to build responsive, searchable, and beautifully designed websites.

This is one of the most frustrating article I've ever written (Weird way to start an article, right?) ๐Ÿ˜œ. It was truly a bittersweet experience.

Did I hear you say why? Glad you asked... Now let me lay it down for you.

โœŒ๏ธ Table of Contents

โš™๏ธ Environment Setup (Optional)

I always write my articles with beginners in mind because I was once one, too. If you're already experienced, feel free to skip this section. This guide assumes you're using Ubuntu Linux. The following commands will help you to set up a fresh server environment and serve as a reference point some time in the future because eventually, youโ€™ll probably be needing it again.

๐Ÿ’ก For other Linux distributions: Replace apt with your system's package manager like yum, pkg, apk and so on.

๐Ÿ’ก For Windows users: You can install Windows Subsystem for Linux (WSL) with the command wsl --install in your terminal, access it with your favourite code editor and then run the commands below.

# Update package lists and upgrade existing packages
sudo apt -y update && sudo apt -y upgrade

# Install Docker, Docker Compose, and add current user to the docker group for non-root access
sudo apt -y install docker.io
sudo apt -y install docker-compose
sudo usermod -aG docker $USER

# Install useful dependencies
sudo apt -y install tree
sudo apt -y install vim

# Install Python 3.10 virtual environment package
sudo apt -y install python3.10-venv

# Install pip for Python 3
sudo apt -y install python3-pip

# Install OpenSSH server and enable/start the SSH service
sudo apt -y install openssh-server
sudo systemctl enable ssh
sudo systemctl start ssh

# Allow SSH through the firewall and enable UFW (Uncomplicated Firewall)
sudo ufw allow ssh
sudo ufw enable

# Generate a new RSA SSH key pair with no passphrase and a comment for identification
ssh-keygen -t rsa -f ~/.ssh/id_rsa -N "" -C "ssh-key"

# Reboot the system to apply changes
sudo shutdown -r now

Enter fullscreen mode Exit fullscreen mode

Now that we're done with running the above commands and restarting the server so changes take full effect, we will be moving to the next section which will be...

๐Ÿ“ฆ Project Scaffolding

In this section, we will setting up the project structure by creating and executing the setup.sh

# Create & make setup script executable
touch setup.sh && chmod +x setup.sh

# Excute setup script (populate script from github link provided)
./setup.sh
Enter fullscreen mode Exit fullscreen mode

After successful execution, your project directory should look like this:

portfolio/
โ”œโ”€โ”€ .github
โ”œโ”€โ”€ .gitignore
โ”œโ”€โ”€ Journal.md
โ”œโ”€โ”€ docs
โ”œโ”€โ”€ mkdocs.yml
โ”œโ”€โ”€ requirements.txt
โ””โ”€โ”€ site
Enter fullscreen mode Exit fullscreen mode

To activate your virtual environment and start the project, run the following commands:

# Activate virtual environment
source env/bin/activate

# Navigate into the project folder
cd portfolio

# Start the development serve
mkdocs serve
Enter fullscreen mode Exit fullscreen mode
INFO    -  Building documentation...
INFO    -  Cleaning site directory
INFO    -  Documentation built in 0.31 seconds
INFO    -  [17:59:56] Watching paths for changes: 'docs', 'mkdocs.yml'
INFO    -  [17:59:56] Serving on http://127.0.0.1:8000/
Enter fullscreen mode Exit fullscreen mode

If you happen to be running on a VM or an external server. You can use this instead, as it binds the IP to the host to make it accessible on your web browser.

mkdocs serve -a "$(hostname -I | awk '{print $1}'):8000"
Enter fullscreen mode Exit fullscreen mode

Head to the project URL, and you should see the screen below in your browser, which means the portfolio template is now up and running! ๐Ÿš€

Project Display

ย 

๐Ÿ“Œ When the foundation is not solid, everything built upon it will crumble in due time.
ย 

The setup script that was run established a solid foundation. However, if you want to build on top of that, this reference page is crucial: https://squidfunk.github.io/mkdocs-material/customization/ because itโ€™s what will take your portfolio from what you see above to what you'll see below ๐Ÿ‘‡.

Slide

๐Ÿคฌ Frustrations (Optional)

This tutorial might look cute, but let me tell you, there was a whooooole lotta frustration trying to figure out where to put what, getting things to work, and why something that worked perfectly on local breaks in production. (That kind of mess)

It got so annoying that I ended up writing a script to scaffold the whole project from scratch because, just like a Microsoft Word CV, one tiny tweak can break the whole format, and suddenly you're spending hours trying to reformat stuff that shouldn't even be broken in the first place. And ohhhh! Don't even get me started on setting up Github Actions, that was the real rabbit hole 2.0. What I thought would take me just a few hours to figure out ended up taking a week.

๐Ÿšถโ€โ™‚๏ธ Walk Through

Wheeeew! Now that Iโ€™ve got that off my chest. Allow me, your one and only tour guide, MrChike, to take you on a cruise through the inner workings of this project.
Ready, Player One? ๐ŸŽฎ

  • docs/: Your development environment (HTML, CSS & JavaScript)
    • overrides/: Customise any part of the site to your taste

๐Ÿ’ก Treat the markdowns(*.md) files like HTML: you can add HTML tags like div to it, and it would still work because it would be converted to index.html, as you will see under site/

docs/
โ”œโ”€โ”€ articles.md
โ”œโ”€โ”€ assets
โ”‚   โ”œโ”€โ”€ images
โ”‚   โ”‚   โ””โ”€โ”€ logo.svg
โ”‚   โ”œโ”€โ”€ javascripts
โ”‚   โ”‚   โ””โ”€โ”€ extra.js
โ”‚   โ””โ”€โ”€ stylesheets
โ”‚       โ””โ”€โ”€ extra.css
โ”œโ”€โ”€ certifications.md
โ”œโ”€โ”€ contributions.md
โ”œโ”€โ”€ index.md
โ”œโ”€โ”€ overrides
โ”‚   โ””โ”€โ”€ partials
โ”‚       โ””โ”€โ”€ logo.html
โ””โ”€โ”€ projects.md
Enter fullscreen mode Exit fullscreen mode
  • sites/: Production environment

๐Ÿ’ก This folder is intended for public access. You don't need to modify it manually as it's regenerated before each deployment. That's why it's included in .gitignore. This folder is what gets deployed to the gh-pages branch and displayed on your portfolio website.

site/
โ”œโ”€โ”€ 404.html
โ”œโ”€โ”€ articles
โ”‚   โ””โ”€โ”€ index.html
โ”œโ”€โ”€ assets
โ”‚   โ”œโ”€โ”€ images
โ”‚   โ”œโ”€โ”€ javascripts
โ”‚   โ””โ”€โ”€ stylesheets
โ”œโ”€โ”€ certifications
โ”‚   โ””โ”€โ”€ index.html
โ”œโ”€โ”€ contributions
โ”‚   โ””โ”€โ”€ index.html
โ”œโ”€โ”€ css
โ”‚   โ”œโ”€โ”€ base.css
โ”‚   โ”œโ”€โ”€ bootstrap.min.css
โ”‚   โ”œโ”€โ”€ bootstrap.min.css.map
โ”‚   โ”œโ”€โ”€ brands.min.css
โ”‚   โ”œโ”€โ”€ fontawesome.min.css
โ”‚   โ”œโ”€โ”€ solid.min.css
โ”‚   โ””โ”€โ”€ v4-font-face.min.css
โ”œโ”€โ”€ img
โ”‚   โ”œโ”€โ”€ favicon.ico
โ”‚   โ””โ”€โ”€ grid.png
โ”œโ”€โ”€ index.html
โ”œโ”€โ”€ js
โ”‚   โ”œโ”€โ”€ base.js
โ”‚   โ”œโ”€โ”€ bootstrap.bundle.min.js
โ”‚   โ”œโ”€โ”€ bootstrap.bundle.min.js.map
โ”‚   โ””โ”€โ”€ darkmode.js
โ”œโ”€โ”€ overrides
โ”‚   โ””โ”€โ”€ partials
โ”œโ”€โ”€ projects
โ”‚   โ””โ”€โ”€ index.html
โ”œโ”€โ”€ search
โ”‚   โ”œโ”€โ”€ lunr.js
โ”‚   โ”œโ”€โ”€ main.js
โ”‚   โ”œโ”€โ”€ search_index.json
โ”‚   โ””โ”€โ”€ worker.js
โ”œโ”€โ”€ sitemap.xml
โ”œโ”€โ”€ sitemap.xml.gz
โ””โ”€โ”€ webfonts
Enter fullscreen mode Exit fullscreen mode
  • deploy.yml: Github Actions Configuration file for automated deployment
.github/
โ””โ”€โ”€ workflows
    โ””โ”€โ”€ deploy.yml
Enter fullscreen mode Exit fullscreen mode
  • Journal.md: A log of your experiences and insights while working on your projects. (Keeping a journal is a great habit to develop when working on projects.)
  • mkdocs.yml: Configuration file for MKDocs
  • requirements.txt: Lists your project dependencies. (It's good practice to include specific versions, as dependencies are frequently updated and changes may break your project in the future.)
.
โ”œโ”€โ”€ Journal.md
โ”œโ”€โ”€ mkdocs.yml
โ””โ”€โ”€ requirements.txt
Enter fullscreen mode Exit fullscreen mode

Now you have a good grasp of the project structure, let's advance to deployment.

๐Ÿš€ Deployment (Github Actions Included)

In this section, we will focus on both manual and automated deployment but first i will lay down the steps in order to follow.

  • ๐Ÿ“ Create a GitHub Repository (if not existing already)
  • ๐Ÿ”‘ Generate SSH keys
  • ๐Ÿ—๏ธ Add your SSH public key to GitHub
  • ๐Ÿง‘โ€๐Ÿ’ป Manual Deployment using mkdocs gh-deploy
  • ๐Ÿ” Create a Personal Access Token (PAT)
  • ๐Ÿคซ Add the PAT as a GitHub Secret
  • ๐Ÿค– Automated Deployment using GitHub Actions

๐Ÿ“ Create a GitHub Repository

Take Home Assignment: Figure it out! ๐Ÿ˜‰

๐Ÿ”‘ Generate SSH keys

  • First, confirm if you have SSH keys available
ls -lh ~/.ssh/
Enter fullscreen mode Exit fullscreen mode
-rw------- 1 mrchike mrchike 2.6K Jun 20 05:26 id_rsa
-rw-r--r-- 1 mrchike mrchike  572 Jun 20 05:26 id_rsa.pub
-rw------- 1 mrchike mrchike  978 Jun 20 05:28 known_hosts
-rw-r--r-- 1 mrchike mrchike  142 Jun 20 05:16 known_hosts.old
Enter fullscreen mode Exit fullscreen mode

๐Ÿ’ก The id_rsa.pub is your authentication public key to access external servers (in this case, GitHub).

โš ๏ธ Note on SSH Keys:
If you try using a custom name for your SSH key (i.e something other than id_rsa.pub), GitHub may reject the connection during deployment. If you encounter this issue, a simple workaround is to generate a new key and overwrite the existing one using the command:

ssh-keygen
Enter fullscreen mode Exit fullscreen mode

This will recreate id_rsa and id_rsa.pub, which are the default names GitHub expects unless otherwise configured.

You will need to register it on your GitHub account to have access to push/pull changes. So run the following command to print it out.

cat ~/.ssh/id_rsa.pub
Enter fullscreen mode Exit fullscreen mode
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDKiPnhAeI70ooWzt7yDgopgmgllifUBwvev1RKBq/UM1rQ/std0PHokt+qcGZlIAtFWUsGdOZi+FDLpOywsL1+XO4UlBVW4M/qQasoVnE/CdT77GxaZQ0btYlHm5FS4mKYRWtxSIxjq3b+qRWB6U4KFkfOG3iQAinLI+iD0olcAikCaA8mRKTg541+qDxY33oSE3a9svjP8keVmB9ALN6zdGoDuLa3gX9OCK8wIOzewYDFfr3wMHpT/oJ0sG5oPtBFMBDHJcdt46T+u3uZCbaRs/Hre7UbYXgXkcQWNZ8fpSj4JieebjP8MrHxkRVG5mmM5/wumaC342fmTBbiA4wJUhOErR7zX/SDlUN5NDAyBiq7vDhdkVrj3sTisXgGnboRhLbrOt0TzAo3QRxyXQTZe7rQPh+9D/DDLgGuVz0PB0YOvSDcbVpWwsZvWzE+oBJi9Xxp3bkJH79+6M/AUZ36D9qd1jzI8sTf1+LNj8nxdNtIWmDtpWeRIIbgBcfusvs= YOUR-SSH-KEY

Enter fullscreen mode Exit fullscreen mode

Now, copy your SSH Key as you'll be needing it for the next step.

๐Ÿ—๏ธ Add your SSH public key to GitHub

You can register your SSH key in one of two ways:

  • User-wide access: https://github.com/settings/keys
  • Repo-specific access (Deploy Key): https://github.com/YOUR-USERNAME/REPO-NAME/settings/keys
    • e.g https://github.com/MrChike/temp/settings/keys

๐Ÿ’ก Your use case should determine your preference.

If you want to use the key across multiple repositories from the same machine, go with user-wide access.

If you only want to allow access to a single repository (i.e for CI/CD purposes), then use a repo-specific deploy key.

Once you've decided on your preference, add your SSH Key.

๐Ÿ“Œ For Repo-specific access (Deploy Key) make sure you tick Allow write access

Congrats! ๐ŸŽ‰ It's time to see our first deployment in action.

๐Ÿง‘โ€๐Ÿ’ป Manual Deployment using mkdocs gh-deploy

First we will start by initializing git in our project forlder

git init
Enter fullscreen mode Exit fullscreen mode
hint: Using 'master' as the name for the initial branch. This default branch name
hint: is subject to change. To configure the initial branch name to use in all
hint: of your new repositories, which will suppress this warning, call:
hint: 
hint:   git config --global init.defaultBranch <name>
hint: 
hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and
hint: 'development'. The just-created branch can be renamed via this command:
hint: 
hint:   git branch -m <name>
Initialized empty Git repository in /path/to/project/portfolio/.git/
Enter fullscreen mode Exit fullscreen mode

Then set your preferred default branch name. Feel free to name it whatever you want but in this tutorial i will be going with master.

git config --global init.defaultBranch master
Enter fullscreen mode Exit fullscreen mode

Now that we've done that, let's connect to our remote repo, commit changes and push to the master branch. Also, note that all GitHub-related URLs should be yours, I only use this tutorial repo to serve as a guide.

# Connects your local repo to the remote GitHub repository via SSH
git remote add origin git@github.com:MrChike/mkdocs-portfolio.git

# Stages all changes and commits them with a message
git add . && git commit -m "Initial Commit"

# Pushes the 'master' branch to GitHub and sets it as the upstream branch
git push --set-upstream origin master
Enter fullscreen mode Exit fullscreen mode
[master (root-commit) 516f9ae] Initial Commit
 14 files changed, 159 insertions(+)
 create mode 100644 .github/workflows/deploy.yml
 create mode 100644 .gitignore
 create mode 100644 Journal.md
 create mode 100644 docs/articles.md
 create mode 100644 docs/assets/images/logo.svg
 create mode 100644 docs/assets/javascripts/extra.js
 create mode 100644 docs/assets/stylesheets/extra.css
 create mode 100644 docs/certifications.md
 create mode 100644 docs/contributions.md
 create mode 100644 docs/index.md
 create mode 100644 docs/overrides/partials/logo.html
 create mode 100644 docs/projects.md
 create mode 100644 mkdocs.yml
 create mode 100644 requirements.txt

Enumerating objects: 24, done.
Counting objects: 100% (24/24), done.
Delta compression using up to 12 threads
Compressing objects: 100% (11/11), done.
Writing objects: 100% (24/24), 3.23 KiB | 826.00 KiB/s, done.
Total 24 (delta 0), reused 0 (delta 0), pack-reused 0
To github.com:MrChike/temp.git
 * [new branch]      master -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.
Enter fullscreen mode Exit fullscreen mode

Now that we've pushed, let's deploy our site by running the command:

mkdocs gh-deploy --force
Enter fullscreen mode Exit fullscreen mode
INFO    -  Cleaning site directory
INFO    -  Building documentation to directory: /path/to/portfolio/site
INFO    -  Documentation built in 0.39 seconds
WARNING -  Version check skipped: No version specified in previous deployment.
INFO    -  Copying '/path/to/portfolio/site' to 'gh-pages' branch and pushing to GitHub.
Enumerating objects: 72, done.
Counting objects: 100% (72/72), done.
Delta compression using up to 12 threads
Compressing objects: 100% (63/63), done.
Writing objects: 100% (72/72), 579.32 KiB | 1.66 MiB/s, done.
Total 72 (delta 8), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (8/8), done.
remote: 
remote: Create a pull request for 'gh-pages' on GitHub by visiting:
remote:      https://github.com/MrChike/mkdocs-portfolio/pull/new/gh-pages
remote: 
To github.com:MrChike/mkdocs-portfolio.git
 * [new branch]      gh-pages -> gh-pages
INFO    -  Your documentation should shortly be available at: https://MrChike.github.io/mkdocs-portfolio/
Enter fullscreen mode Exit fullscreen mode

Now, the portfolio site should be hosted on the link provided. In this case https://MrChike.github.io/mkdocs-portfolio/

If you have yours set up and working, a huge congratulations!

At this point, you might be wondering: if I can manually deploy my site from my local machine, why bother automating it?
Honestly, I donโ€™t even know why I decided to take it a step further. But this is when I finally understood what Nagato meant when he said to Naruto:
"And now... You shall know pain." ๐Ÿ˜ตโ€๐Ÿ’ซ

๐Ÿ” Create a Personal Access Token (PAT)

Head to your Github account and create your personal access token at https://github.com/settings/personal-access-tokens

Fill in the details as needed. Under Repository access, select your preference (either all repos or just specific ones).
Most importantly, under Permissions, youโ€™ll see two sections: Repository permissions and Account permissions. Go ahead and give everything under both sections read/write access.

Once thatโ€™s done, scroll down and hit Generate token.
it's important that you copy this token and save somewhere because once you leave that page the only way to use it if forgotten is to regenerate it.
ย 

๐Ÿ“Œ NB: It took me a while to figure this one out because without it, youโ€™ll keep running into errors when deploying with GitHub Actions. At some point, I was really losing patience, so I just gave everything under both permission sections read/write access. When you have time, feel free to go back and untick anything you donโ€™t actually need.

ย 

๐Ÿคซ Add the PAT as a GitHub Secret

Head to your repo โ†’ Settings โ†’ Secrets and variables โ†’ Actions, and create a new repository secret for your token.

Populate the following fields:

  • Name: ACCESS_TOKEN
  • Secret: Paste the personal access token you just generated and copied Then click Save.

This gives GitHub Actions access for automated deployments

๐Ÿค– Automated Deployment using GitHub Actions

To wrap things up, we'll automate deployment to run daily at 12:00 AM, ensuring any updates to your portfolio are always live.

But first, letโ€™s give credit where itโ€™s due.
A huge shoutout to peaceiris for simplifying what could have been a tedious process. Check out the repo when you have time, it's worth it!

Now, update your deploy.yml file with the following configuration.

๐Ÿ“ The inline comments explain each section, but here are a few key points to emphasize:

  • branches:
    Update this to match the branch you're deploying from (master, main, or multiple branches if needed). The workflow will only trigger on changes to the specified branch(es).

  • personal_token:
    Do not change this key name. It must be exactly personal_token, thatโ€™s what the GitHub Action expects.
    However, the repository secret name (i.e ACCESS_TOKEN) can be whatever you like, just make sure the name you use in Secrets and variables โ†’ Actions matches what you reference in the workflow.

commit and push your changes to the repo

You can monitor your deployment runs under the Actions tab in your GitHub repo. Each run will show success/failure logs and scheduling history as displayed in the screenshot below..

Github Action

And that's all folks. It's been an interest ride so far and if you have any blockers I'm open to mentor and assist.

Would you like a deeper dive on this topic? ๐Ÿ‘‰ Cast your vote

๐Ÿ’ก Enjoyed this article? Connect with me on:

Your support means a lot, if youโ€™d like to buy me a coffee โ˜•๏ธ to keep me fueled, feel free to check out this link. Your generosity would go a long way in helping me continue to create content like this.

Until next time, happy coding! ๐Ÿ‘จ๐Ÿพโ€๐Ÿ’ป๐Ÿš€

Previously Written Articles:

  • ๐Ÿ”ฅ FastAPI in Production: Build, Scale & Deploy โ€“ Series A: Codebase Design โ†’ Read here
  • How to Learn Effectively & Efficiently as a Professional in any Field ๐Ÿง โฑ๏ธ๐ŸŽฏ โ†’ Read here

Top comments (0)