EasyDeploy
Back to blog
hugo hugo-extended oracle-cloud docker saas static-site-generator

Building Your SaaS Website with Hugo: From Setup to Oracle Cloud Deployment

Łukasz Tomalczyk ·
Building Your SaaS Website with Hugo: From Setup to Oracle Cloud Deployment

Introduction

When building a SaaS application, establishing a strong online presence is crucial. We need a platform where we can publish information about our product, host documentation, and showcase our offerings to potential customers.

Choosing the Right Tool

After researching available options, I narrowed it down to two compelling choices. The first was Ghost - a powerful platform, but it comes with subscription costs. At this stage of my project, minimizing expenses is essential since my SaaS isn’t generating revenue yet. The second option was Hugo, which proved to be the perfect fit.

Why Hugo?

Hugo is a lightning-fast static site generator that allows you to create websites, blogs, and documentation using simple Markdown files. Instead of relying on databases or complex frameworks, Hugo converts .md files into production-ready HTML that can be easily hosted on platforms like GitHub Pages, Netlify, Vercel, or in our case - Oracle Cloud.

Key advantages:

Our Deployment Strategy

Our goal is to deploy the website in a Docker container on Oracle Cloud’s Always Free tier, providing a robust hosting solution without ongoing costs.

Step-by-Step Installation Guide

Prerequisites

I’m working on Windows with Chocolatey package manager installed. We’ll use it to install Hugo CLI efficiently.

Note: If you don’t have Chocolatey installed, visit chocolatey.org for installation instructions.

Installing Hugo Extended

Open a terminal with administrator privileges and run:

choco install hugo-extended -y

Why Hugo Extended? This is the extended version of Hugo, which is essential for our use case. Hugo Extended includes:

These features are required by most modern Hugo themes, including the one we’ll be using.

Creating a New Hugo Project

Initialize your new Hugo site:

hugo new site EasyDeploySaaS
cd EasyDeploySaaS

Adding the Theme

We’ll use the hugo-saasify-theme, a modern theme designed specifically for SaaS websites:

git init
git submodule add https://github.com/lukasztomalczyk/hugo-saasify-theme themes/hugo-saasify-theme

Initial Configuration

Create a basic configuration file (hugo.toml):

baseURL = "https://your-domain.com"
title = "Your SaaS Name"
theme = "hugo-saasify-theme"

[module]
  [module.hugoVersion]
    extended = true
    min = "0.80.0"

Testing Your Setup

Start the development server to verify everything works:

hugo server -D

Your site should now be available at http://localhost:1313.

Building Your Site

Once you have your content ready, build the static files:

hugo --minify

This generates optimized static files in the public/ directory, ready for deployment.

Deploying to Oracle Cloud

Now let’s deploy our Hugo site to Oracle Cloud using Docker and Azure Pipelines.

Creating the Dockerfile

Create a Dockerfile in your project root with multi-stage build:

# Stage 1: Build Hugo + TailwindCSS
FROM hugomods/hugo:debian-std-exts-0.152.2 AS builder

WORKDIR /src
COPY . .

# Install theme dependencies (TailwindCSS / PostCSS)
RUN npm install

# Build CSS/JS and generate Hugo site
RUN npm run build

# Stage 2: Serve with Nginx
FROM nginx:alpine

# Remove default content
RUN rm -rf /usr/share/nginx/html/*

# Copy built Hugo files
COPY --from=builder /src/public /usr/share/nginx/html

# Copy custom Nginx configuration
COPY nginx.conf /etc/nginx/nginx.conf

# Expose custom port
EXPOSE 8091

# Start Nginx
CMD ["nginx", "-g", "daemon off;"]

Azure Pipeline Configuration

We’ll use Azure Pipelines for automated deployment. You’ll need to:

  1. Set up SSH connector in Azure DevOps
  2. Configure server connection details

Pipeline 1: Build and Push Docker Image

Create website-azure-pipeline-compile.yml:

trigger: none

parameters:
  - name: repository
    displayName: Repository
    type: string
    default: 'Production'
    values:
      - Production
      - Test

variables:
  docker_tag: '${{ parameters.repository }}'
  app_name: 'EasyDeployWebsite'
  app_name_lower: 'easy-deploy-website'

resources:
- repo: self

stages:
- stage: Build
  displayName: Build Multiarch Docker Image
  jobs:
  - job: Build
    displayName: Build and Push Multiarch Image
    pool:
      vmImage: ubuntu-latest
    steps:
    - checkout: self
      submodules: true

    # Docker Hub login
    - task: Docker@2
      displayName: Docker Login
      inputs:
        command: login
        containerRegistry: 'hub.docker.com'

    # Setup QEMU for ARM emulation
    - script: |
        docker run --rm --privileged tonistiigi/binfmt --install all
      displayName: Setup QEMU for multiarch

    # Create buildx builder
    - script: |
        docker buildx create --use --name mybuilder
        docker buildx inspect --bootstrap
      displayName: Setup Docker Buildx

    # Build and push multiarch image
    - script: |
        docker buildx build \
          --platform linux/arm64 \
          -f $(Build.SourcesDirectory)/src/Dockerfile \
          -t lukasztomalczyk/sp-craft.$(app_name_lower)-arm64:$(Build.BuildId) \
          -t lukasztomalczyk/sp-craft.$(app_name_lower)-arm64:$(docker_tag) \
          $(Build.SourcesDirectory)/src \
          --push
      displayName: Build and Push Multiarch Docker Image

Pipeline 2: Deploy to Server

Create website-azure-pipeline-install.yml:

trigger: none

parameters:
  - name: server
    displayName: Environment
    type: string
    default: 'Production'
    values:
      - Production
      - Test

variables:
- name: environment
  value: '${{ parameters.server }}'
- name: app_name
  value: 'EasyDeployWebsite'
- name: server_username
  value: 'opc'

resources:
- repo: self

stages:
- stage: Deploy
  displayName: Deploy to Server
  jobs:
  - job: Deploy
    displayName: Deploy and Run
    pool:
      vmImage: ubuntu-latest
    steps:
    - task: SSH@0
      displayName: Create required folders
      inputs:
        sshEndpoint: 'SSH-$(environment)'
        runOptions: 'commands'
        commands: >
          mkdir -p /home/$(server_username)/$(environment)/SpCraft.$(app_name)
        readyTimeout: '20000'

    - task: replacetokens@6
      displayName: Replace tokens in docker-compose
      inputs:
        root: '$(Build.SourcesDirectory)/src'
        sources: 'docker-compose.yml'
        tokenPattern: 'doublebraces'
        missingVarAction: 'keep'

    - task: CopyFilesOverSSH@0
      displayName: Copy docker-compose.yml
      inputs:
        sshEndpoint: 'SSH-$(environment)'
        sourceFolder: '$(Build.SourcesDirectory)/src'
        contents: |
          docker-compose.yml
        targetFolder: >
          /home/$(server_username)/$(environment)/SpCraft.$(app_name)
        readyTimeout: '20000'

    - task: SSH@0
      displayName: Stop existing container
      continueOnError: true
      inputs:
        sshEndpoint: 'SSH-$(environment)'
        runOptions: 'commands'
        commands: >
          docker-compose
          -f /home/$(server_username)/$(environment)/SpCraft.$(app_name)/docker-compose.yml
          down >/dev/null 2>&1
        readyTimeout: '20000'

    - task: SSH@0
      displayName: Pull latest images
      continueOnError: true
      inputs:
        sshEndpoint: 'SSH-$(environment)'
        runOptions: 'commands'
        commands: >
          docker-compose
          -f /home/$(server_username)/$(environment)/SpCraft.$(app_name)/docker-compose.yml
          pull >/dev/null 2>&1
        readyTimeout: '20000'

    - task: SSH@0
      displayName: Start container
      inputs:
        sshEndpoint: 'SSH-$(environment)'
        runOptions: 'commands'
        commands: >
          docker-compose
          -f /home/$(server_username)/$(environment)/SpCraft.$(app_name)/docker-compose.yml
          up -d >/dev/null 2>&1
        readyTimeout: '20000'

Environment Management

Note: This setup includes environment separation (Production/Test). For simpler deployments, you can:

Key Benefits

Conclusion

Hugo Extended provides an excellent foundation for building fast, scalable SaaS websites. The static nature of Hugo sites ensures lightning-fast performance and minimal server resource usage, making it perfect for modern web applications.

Next Steps

  1. Customize your theme to match your brand
  2. Create compelling content for your target audience
  3. Set up deployment to your preferred hosting platform
  4. Implement analytics and SEO optimization

Resources