Deploying a Jekyll Site
Jekyll is a simple, blog-aware static site generator written in Ruby that transforms Markdown and Liquid templates into static websites. It features a powerful templating system, theme support, plugin ecosystem, collections for organizing content, data files for structured information, and built-in support for drafts and future posts. Jekyll is ideal for building blogs, documentation sites, portfolios, and any project-driven website where simplicity and customization are important. With Jekyll, you get a straightforward development experience, excellent integration with GitHub Pages, and complete control over your site’s structure and design.
This comprehensive guide walks through deploying a Jekyll site to Klutch.sh using either Nixpacks (automatic zero-configuration deployment) or a Dockerfile (manual container control). You’ll learn how to scaffold a Jekyll project, install and customize themes, create content using Markdown, build custom layouts and includes, configure data files and collections, optimize images, set up environment variables, implement security best practices, set up monitoring, configure custom domains, and troubleshoot common issues. By the end of this guide, you’ll have a production-ready Jekyll site running on Klutch.sh’s global infrastructure with automatic HTTPS, optimized performance, and reliable hosting.
Prerequisites
- Ruby (version 2.7+) – Download Ruby
- Bundler gem for dependency management – Bundler Documentation
- Git installed locally and a GitHub account (Klutch.sh uses GitHub as the only git source)
- Klutch.sh account with access to the dashboard at klutch.sh/app
- Basic knowledge of Ruby, Markdown, Liquid templating, and YAML
Getting Started: Create a Jekyll Site
1. Install Ruby and Bundler
First, ensure Ruby is installed:
ruby --versionInstall Bundler if you don’t have it:
gem install bundler2. Create a New Jekyll Site
Create a new Jekyll site:
bundle exec jekyll new my-jekyll-sitecd my-jekyll-siteOr create a blank site:
jekyll new --blank my-jekyll-sitecd my-jekyll-site3. Project Structure
A typical Jekyll site structure looks like:
my-jekyll-site/├── _data/│ ├── authors.yml│ └── navigation.yml├── _drafts/│ └── unpublished-post.md├── _includes/│ ├── header.html│ ├── footer.html│ ├── nav.html│ └── post-card.html├── _layouts/│ ├── default.html│ ├── page.html│ ├── post.html│ └── home.html├── _posts/│ ├── 2025-01-15-hello-world.md│ └── 2025-01-20-another-post.md├── _sass/│ ├── base.scss│ ├── layout.scss│ └── syntax.scss├── assets/│ ├── css/│ │ └── main.scss│ └── images/│ └── logo.png├── pages/│ ├── about.md│ └── contact.md├── _config.yml├── Gemfile├── Gemfile.lock├── index.md└── Dockerfile4. Configure Your Site
Update _config.yml:
# Site settingstitle: My Jekyll Siteemail: hello@example.comdescription: A simple, blog-aware static site built with Jekyllbaseurl: ""url: "https://example-app.klutch.sh"repository: your-username/your-repo
# Build settingstheme: minimaplugins: - jekyll-feed - jekyll-seo-tag - jekyll-sitemap
# Frontmatter defaultsdefaults: - scope: path: "" type: "posts" values: layout: post author: "Your Name" categories: [] tags: []
# Pagination (requires jekyll-paginate)paginate: 10paginate_path: "/blog/page:num/"
# Collectionscollections: projects: output: true permalink: /projects/:name/ recipes: output: true permalink: /recipes/:name/
# Exclude from processingexclude: - .gitignore - README.md - Gemfile - Gemfile.lock - node_modules - vendor/bundle/ - vendor/cache/ - vendor/gems/ - vendor/ruby/5. Set Up Gemfile
Create a Gemfile for managing dependencies:
source "https://rubygems.org"
gem "jekyll", "~> 4.3"gem "minima", "~> 2.5"
group :jekyll_plugins do gem "jekyll-feed", "~> 0.17" gem "jekyll-seo-tag", "~> 2.8" gem "jekyll-sitemap", "~> 1.4" gem "jekyll-paginate", "~> 1.1" gem "jekyll-relative-links", "~> 0.7"end
group :development do gem "webrick", "~> 1.7"endInstall gems:
bundle install6. Create Content
Create your first blog post at _posts/2025-01-15-hello-klutch.md:
---layout: posttitle: "Hello from Jekyll on Klutch.sh!"date: 2025-01-15categories: ["jekyll", "deployment"]tags: ["static-site", "klutch"]author: "Your Name"description: "My first post on Jekyll deployed to Klutch.sh"---
# Welcome to Jekyll on Klutch.sh!
This is my first blog post using Jekyll. Jekyll is simple, blog-aware, and perfect for static site generation.
## Why Jekyll?
- **Simple**: Just Markdown, Liquid, and YAML- **Fast**: Static HTML generation- **Flexible**: Complete control over design and structure- **GitHub Integration**: Seamless GitHub Pages integration
Enjoy deploying Jekyll on Klutch.sh!Create a page at pages/about.md:
---layout: pagetitle: "About"permalink: /about/---
# About This Site
Welcome to my Jekyll site deployed on Klutch.sh!
I use Jekyll because it's:- **Simple**: Markdown-based content- **Fast**: Generates static HTML- **Flexible**: Custom Liquid templates
Learn more at [jekyllrb.com](https://jekyllrb.com).7. Create Custom Layouts
Create a custom layout at _layouts/post.html:
---layout: default---
<article class="post"> <header class="post-header"> <h1 class="post-title">{{ page.title }}</h1> <div class="post-meta"> <time datetime="{{ page.date | date: '%Y-%m-%dT%H:%M:%SZ' }}" class="post-date"> {{ page.date | date: "%B %d, %Y" }} </time> {% if page.author %} <span class="post-author">by {{ page.author }}</span> {% endif %} </div> </header>
<div class="post-content"> {{ content }} </div>
{% if page.tags %} <footer class="post-footer"> <ul class="post-tags"> {% for tag in page.tags %} <li> <a href="/tags/{{ tag | slugify }}" class="tag">{{ tag }}</a> </li> {% endfor %} </ul> </footer> {% endif %}</article>
{% if site.paginate %} <nav class="pagination"> {% if paginator.previous_page %} <a href="{{ paginator.previous_page_path }}" class="prev">← Newer Posts</a> {% endif %} {% if paginator.next_page %} <a href="{{ paginator.next_page_path }}" class="next">Older Posts →</a> {% endif %} </nav>{% endif %}Create an include at _includes/post-card.html:
<article class="post-card"> <h3><a href="{{ post.url }}">{{ post.title }}</a></h3> <p class="post-meta"> <time datetime="{{ post.date | date: '%Y-%m-%dT%H:%M:%SZ' }}"> {{ post.date | date: "%B %d, %Y" }} </time> </p> <p class="post-excerpt">{{ post.excerpt }}</p> <a href="{{ post.url }}" class="read-more">Read More →</a></article>8. Create Data Files
Create _data/authors.yml:
default: name: Your Name email: hello@example.com bio: A Jekyll developer deploying to Klutch.sh avatar: /assets/images/avatar.jpg
john: name: John Doe email: john@example.com bio: Technical writer and Jekyll enthusiast avatar: /assets/images/john.jpg
jane: name: Jane Smith email: jane@example.com bio: Full-stack developer and open source contributor avatar: /assets/images/jane.jpgCreate _data/navigation.yml:
- name: Home url: /- name: Blog url: /blog/- name: About url: /about/- name: Projects url: /projects/- name: Contact url: /contact/9. Create Collections
Create a projects collection by adding to _config.yml:
collections: projects: output: true permalink: /projects/:name/Create _projects/my-project.md:
---title: "My Awesome Project"description: "An amazing project built with Jekyll"image: /assets/images/project.jpgtags: ["jekyll", "ruby"]live_url: "https://example.com"github_url: "https://github.com/user/project"---
# My Awesome Project
This is my project built with Jekyll. It demonstrates the power of static site generation.
## Features
- Blazing fast performance- SEO optimized- Mobile responsive- Easy to maintain
View the live site at [example.com](https://example.com).10. Run the Development Server
Start the Jekyll development server:
bundle exec jekyll serveOr with drafts:
bundle exec jekyll serve --draftsYour site will be available at http://localhost:4000. Jekyll automatically reloads on changes.
11. Use Liquid Filters and Tags
Use Liquid in your templates:
<!-- Capitalize filter --><h1>{{ page.title | capitalize }}</h1>
<!-- Join filter --><p>Tags: {{ page.tags | join: ", " }}</p>
<!-- For loops -->{% for post in site.posts limit: 5 %} {% include post-card.html post=post %}{% endfor %}
<!-- Conditional logic -->{% if page.categories %} <p>Categories: {% for category in page.categories %} {{ category }}{% unless forloop.last %},{% endunless %} {% endfor %} </p>{% endif %}12. Build for Production
Build your Jekyll site:
bundle exec jekyll buildThe built site will be in the _site/ directory.
Local Production Build Test
Before deploying, test the production build:
bundle exec jekyll build --futurebundle exec jekyll serve --skip-initial-build --host 127.0.0.1 --port 4000 _siteOr serve the built directory:
cd _sitepython3 -m http.server 8080# ornpx http-serverVisit http://localhost:8080 to verify the production build.
Deploying with Nixpacks
Nixpacks automatically detects your Ruby/Jekyll site and configures build and runtime environments without requiring a Dockerfile. This is the simplest deployment method for Jekyll.
Prerequisites for Nixpacks Deployment
- Your Jekyll site pushed to a GitHub repository
- Valid
_config.ymlconfiguration file - Valid
GemfileandGemfile.lockfor dependency management - No
Dockerfilein the repository root (if one exists, Klutch.sh will use Docker instead)
Steps to Deploy with Nixpacks
-
Push Your Jekyll Site to GitHub
Initialize and push your project to GitHub:
Terminal window git initgit add .git commit -m "Initial Jekyll site"git branch -M maingit remote add origin git@github.com:YOUR_USERNAME/YOUR_REPO.gitgit push -u origin main -
Log In to Klutch.sh Dashboard
Go to klutch.sh/app and sign in with your GitHub account.
-
Create a Project
Navigate to the Projects section and create a new project for your Jekyll site.
-
Create an App
Click “Create App” and select your GitHub repository.
-
Select the Branch
Choose the branch you want to deploy (typically
main). -
Configure Traffic Type
Select HTTP as the traffic type for Jekyll (a static site generator).
-
Set the Internal Port
Set the internal port to
4000– this is the default port where Jekyll serves sites. -
Add Environment Variables (Optional)
Add any environment variables your Jekyll site requires:
JEKYLL_ENV=productionJEKYLL_FUTURE=falseJEKYLL_DRAFTS=falseSITE_URL=https://example-app.klutch.shIf you need to customize the Nixpacks build or start command, use these environment variables:
BUILD_COMMAND: Override the default build command (e.g.,bundle exec jekyll build)START_COMMAND: Override the default start command (e.g.,bundle exec jekyll serve --host 0.0.0.0)
-
Configure Compute Resources
Select your region, compute size, and number of instances based on expected traffic.
-
Deploy
Click “Create” to start the deployment. Nixpacks will automatically build and deploy your Jekyll site. Your site will be available at a URL like
https://example-app.klutch.sh.
Deploying with Docker
For more control over your deployment environment, you can use a Dockerfile. Klutch.sh automatically detects a Dockerfile in your repository root and uses it for deployment.
Creating a Dockerfile for Jekyll
Create a Dockerfile in the root of your Jekyll site:
# === Build stage ===FROM ruby:3.3-alpine AS builder
WORKDIR /site
# Install build dependenciesRUN apk add --no-cache build-base
# Copy Gemfile and install gemsCOPY Gemfile Gemfile.lock ./RUN bundle config set --local deployment 'true' && \ bundle install --jobs 4 --retry 3
# Copy site files and buildCOPY . .RUN bundle exec jekyll build --strict_front_matter
# === Runtime stage ===FROM nginx:alpine
COPY --from=builder /site/_site /usr/share/nginx/html
# Configure Nginx for proper static site servingRUN echo 'server { \ listen 80; \ server_name _; \ root /usr/share/nginx/html; \ index index.html; \ error_page 404 /404.html; \ location / { \ try_files $uri $uri/ $uri.html =404; \ } \ location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { \ expires 1y; \ add_header Cache-Control "public, immutable"; \ } \}' > /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]Alternative Dockerfile Using http-server
For a lightweight alternative using Node.js http-server:
# === Build stage ===FROM ruby:3.3-alpine AS builder
WORKDIR /site
RUN apk add --no-cache build-base
COPY Gemfile Gemfile.lock ./RUN bundle config set --local deployment 'true' && \ bundle install --jobs 4 --retry 3
COPY . .RUN bundle exec jekyll build --strict_front_matter
# === Runtime stage ===FROM node:18-alpine
WORKDIR /app
RUN npm install -g http-server
COPY --from=builder /site/_site ./
ENV PORT=4000EXPOSE 4000
CMD ["http-server", ".", "-p", "4000", "--gzip"]Dockerfile Notes
- Builder stage: Uses Ruby image to install dependencies and build the Jekyll site.
- Runtime stage: Uses Nginx (recommended) or http-server to serve static files.
- Port: The
PORTenvironment variable is set to4000for http-server or80for Nginx. - Multi-stage build: Reduces final image size by excluding build tools from the runtime container.
- Caching headers: Nginx configuration includes browser caching for optimal performance.
Steps to Deploy with Docker
-
Create a Dockerfile
Add the Dockerfile (shown above) to the root of your Jekyll repository.
-
Test Locally (Optional)
Build and test the Docker image locally:
Terminal window docker build -t jekyll-site:latest .docker run -p 4000:4000 jekyll-site:latestVisit http://localhost:4000 to verify.
-
Push to GitHub
Commit and push the Dockerfile and your code:
Terminal window git add Dockerfilegit commit -m "Add Dockerfile for production deployment"git push origin main -
Create an App in Klutch.sh
Go to klutch.sh/app, navigate to “Create App”, and select your repository.
-
Configure the App
- Traffic Type: Select HTTP
- Internal Port: Set to
4000(or80if using the Nginx Dockerfile) - Environment Variables: Add any required runtime variables
-
Deploy
Klutch.sh automatically detects the Dockerfile and uses it to build and deploy your site. Your site will be available at
https://example-app.klutch.sh.
Environment Variables
Define environment variables in the Klutch.sh dashboard for production configuration:
JEKYLL_ENV=productionJEKYLL_FUTURE=falseJEKYLL_DRAFTS=falseSITE_URL=https://example-app.klutch.shANALYTICS_ID=your-analytics-idAccessing Environment Variables in Jekyll
Access environment variables in your templates using Jekyll filters:
{{ site.url }}{{ site.title }}{{ site.description }}Or in _config.yml:
site_url: https://example-app.klutch.shgoogle_analytics: UA-XXXXXXXX-Xenvironment: productionIn templates:
{% if jekyll.environment == 'production' %} <!-- Production only content --> <script> var analyticsID = "{{ site.google_analytics }}"; </script>{% endif %}Persistent Storage
If your Jekyll site generates files, caches data, or needs to store user-generated content, you can use persistent volumes in Klutch.sh.
Adding Persistent Volumes
- In the Klutch.sh dashboard, go to your app’s Volumes section.
- Click Add Volume.
- Set the mount path (e.g.,
/cache,/data,/uploads). - Set the size (e.g.,
1 GiB,5 GiB). - Save and redeploy your app.
Example: Caching Generated Content
If you’re generating dynamic content or caching API responses:
# In your Dockerfile, create a volume mount pointRUN mkdir -p /var/cache/jekyll
VOLUME /var/cache/jekyllSecurity Best Practices
1. HTTPS/SSL Enforcement
Klutch.sh automatically provides HTTPS for all deployed sites. All traffic is encrypted and secure.
2. Security Headers
Add security headers via Nginx configuration in your Dockerfile:
RUN echo 'server { \ listen 80; \ add_header X-Content-Type-Options "nosniff" always; \ add_header X-Frame-Options "DENY" always; \ add_header X-XSS-Protection "1; mode=block" always; \ add_header Referrer-Policy "strict-origin-when-cross-origin" always; \ add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always; \}' > /etc/nginx/conf.d/default.conf3. Content Security Policy
Implement CSP headers to protect against XSS attacks:
<!-- In your default layout --><meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'">4. Disable Directory Listing
Ensure Nginx doesn’t list directories:
RUN echo 'server { \ listen 80; \ autoindex off; \}' > /etc/nginx/conf.d/default.conf5. Protect Sensitive Files
Use .gitignore to prevent accidental commits:
_site/.jekyll-cache/.jekyll-metadata.bundle/vendor/config.local.ymlsecrets/.env.env.local6. Dependency Security
Keep gems updated for security patches:
bundle updatebundle audit7. Input Validation for Forms
If your site includes forms via plugins or custom code, validate input:
{% if page.form %} <form method="POST" action="/submit"> <input type="text" name="name" required> <input type="email" name="email" required> <button type="submit">Submit</button> </form>{% endif %}Monitoring and Logging
Health Check
Jekyll sites are static and reliable. Monitor deployment health through the Klutch.sh dashboard.
Performance Monitoring
Use tools to monitor site performance:
<!-- Add to your default layout -->{% if jekyll.environment == 'production' %}<script> // Web Vitals or other monitoring code</script>{% endif %}Error Tracking
Use a service like Sentry for JavaScript error tracking:
<!-- In your default layout -->{% if jekyll.environment == 'production' %}<script src="https://browser.sentry-cdn.com/7.x.x/bundle.min.js"></script><script> Sentry.init({ dsn: "{{ site.sentry_dsn }}" });</script>{% endif %}Structured Logging
Monitor build logs in the Klutch.sh dashboard for any deployment issues.
Custom Domains
To use a custom domain with your Klutch.sh-deployed Jekyll site:
1. Add the Domain in Klutch.sh
In the Klutch.sh dashboard, go to your app’s settings and add your custom domain (e.g., blog.example.com).
2. Update Your DNS Provider
Update your DNS records with the CNAME provided by Klutch.sh:
CNAME: blog.example.com → example-app.klutch.sh3. Configure Jekyll for Your Domain
Update _config.yml with your custom domain:
url: "https://blog.example.com"4. Wait for DNS Propagation
DNS changes can take up to 48 hours to propagate. Verify with:
nslookup blog.example.com# ordig blog.example.com CNAMEOnce propagated, your Jekyll site will be accessible at your custom domain with automatic HTTPS.
Troubleshooting
Issue 1: Build Fails with “Gem Dependencies”
Error: Could not find gem or bundle install fails
Solutions:
- Ensure
GemfileandGemfile.lockare in the repository root - Update Gemfile.lock:
bundle lock --add-platform x86_64-linux - Check for Ruby version mismatch: specify in
Gemfile:ruby '3.3.0' - Clear bundler cache:
bundle clean --force
Issue 2: Pages Not Appearing
Error: Content pages don’t show on the site
Solutions:
- Ensure files are in correct directories (
_posts/for blog posts) - Check filename format:
YYYY-MM-DD-title.md - Verify front matter is valid YAML
- Check for
draft: truein front matter (remove for publication) - Verify
_config.ymlis properly formatted
Issue 3: Assets Not Loading (404 Errors)
Error: CSS, images, or JavaScript files return 404
Solutions:
- Verify asset paths in templates (use
{{ '/assets/css/main.css' | relative_url }}) - Check files are in
assets/directory - Use Liquid’s
site.baseurlfor path prefix - Rebuild:
bundle exec jekyll clean && bundle exec jekyll build
Issue 4: Slow Build Times
Error: Jekyll build takes more than a minute
Solutions:
- Profile the build:
bundle exec jekyll build --profile - Remove unused plugins from
Gemfile - Optimize large images in source content
- Reduce the number of posts/pages
- Consider using Jekyll incremental builds:
bundle exec jekyll build --incremental
Issue 5: Liquid Template Errors
Error: Undefined variable or filter errors in Liquid templates
Solutions:
- Verify variable names match exactly (case-sensitive)
- Check filter syntax:
{{ variable | filter }} - Use Liquid’s
defaultfilter for fallback:{{ page.author | default: 'Anonymous' }} - Test templates in local development before deploying
Issue 6: Collection Pages Not Generating
Error: Collection items don’t create individual pages
Solutions:
- Ensure
_config.ymlhas collection configured withoutput: true - Verify collection folder name matches config (e.g.,
_projectsforprojectscollection) - Check front matter in collection items
- Verify layout file exists for collection
Best Practices
1. Organize Content Logically
Create a clear content structure:
_posts/ 2025-01/ 2025-02/_pages/ about.md contact.md_projects/ project-1.md project-2.md2. Use Front Matter Consistently
Define consistent front matter for all content:
---layout: posttitle: "Article Title"description: "Short description for SEO"date: 2025-01-15categories: ["blog"]tags: ["tag1", "tag2"]author: "Your Name"image: /assets/images/featured.jpg---3. Create Reusable Includes
Break templates into small, reusable includes:
_includes/ header.html footer.html nav.html post-card.html sidebar.html social.html4. Use Sass for Styling
Organize stylesheets in _sass/:
_sass/ base.scss layout.scss components.scss syntax.scssImport in main stylesheet:
@import "base", "layout", "components", "syntax";5. Implement Data-Driven Content
Use _data/ for structured information:
- name: Alice role: Lead Developer avatar: /images/alice.jpg
- name: Bob role: Designer avatar: /images/bob.jpgUse in templates:
{% for member in site.data.team %} <div class="team-member"> <img src="{{ member.avatar }}" alt="{{ member.name }}"> <h3>{{ member.name }}</h3> <p>{{ member.role }}</p> </div>{% endfor %}6. Optimize Images
Process images for web:
{% assign image = site.static_files | where: "name", "featured.jpg" | first %}<img src="{{ image.path }}" alt="Featured image" loading="lazy">7. Implement Pagination
Enable pagination for large collections:
{% for post in paginator.posts %} {% include post-card.html post=post %}{% endfor %}
{% if paginator.total_pages > 1 %} <nav class="pagination"> {% if paginator.previous_page %} <a href="{{ paginator.previous_page_path }}" class="prev">← Newer</a> {% endif %} {% if paginator.next_page %} <a href="{{ paginator.next_page_path }}" class="next">Older →</a> {% endif %} </nav>{% endif %}8. Create Category and Tag Pages
Generate archive pages for categories and tags:
{% for category in site.categories %} <h2>{{ category[0] }}</h2> <ul> {% for post in category[1] %} <li><a href="{{ post.url }}">{{ post.title }}</a></li> {% endfor %} </ul>{% endfor %}9. Keep Dependencies Updated
Regularly update gems:
bundle updatebundle audit fix10. Use Version Control Effectively
Keep Gemfile.lock in version control for reproducible builds:
git add Gemfile.lockgit commit -m "Update dependencies"git push origin mainVerifying Your Deployment
After deployment completes:
- Check the App URL: Visit your site at
https://example-app.klutch.shor your custom domain. - Verify All Pages Load: Navigate through different sections and pages.
- Check Performance: Use Google PageSpeed Insights to verify performance.
- Review SEO: Check with SEO checkers.
- Test Responsiveness: Verify mobile, tablet, and desktop layouts.
- Check Browser Console: Open F12 and verify no errors.
- Review Klutch.sh Logs: Check the Klutch.sh dashboard logs for issues.
If your site doesn’t work as expected, review the troubleshooting section and check the Klutch.sh dashboard logs.
External Resources
- Official Jekyll Documentation
- Jekyll Themes
- Liquid Template Language
- Klutch.sh Official Website
- Ruby Documentation
- Bundler Documentation
- Web Security Documentation
Deploying a Jekyll site to Klutch.sh is straightforward with Nixpacks for automatic deployment or Docker for custom environments. By following this guide, you’ve learned how to scaffold a Jekyll project, install and customize themes, create content using Markdown, build custom layouts and includes, configure data files and collections, optimize images, configure environment variables, implement security best practices, set up monitoring, and troubleshoot common issues. Your Jekyll site is now running on Klutch.sh’s global infrastructure with automatic HTTPS, optimized performance, and reliable hosting. For additional help or questions, consult the official Jekyll documentation or contact Klutch.sh support.