Skip to main content

Automating Nikola blog deployments with GitHub Actions

Building a Nikola static site is fast. Pushing to S3 is fast. But running them manually every time you publish a post is tedious. GitHub Actions automates both in a few minutes.

The setup we built

A workflow that: 1. Listens for pushes to main 2. Installs Nikola and dependencies 3. Builds the entire site 4. Pushes the output to S3

Every post goes live the moment you push.

Create limited AWS credentials

First, create an IAM user with S3-only access to your bucket. This follows the principle of least privilege:

aws iam create-user --user-name blog-deploy-user
aws iam put-user-policy --user-name blog-deploy-user \
  --policy-name s3-bucket-deploy \
  --policy-document '{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:DeleteObject",
        "s3:GetObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::your-bucket",
        "arn:aws:s3:::your-bucket/*"
      ]
    }
  ]
}'
aws iam create-access-key --user-name blog-deploy-user

Save those credentials. You'll need them in a moment.

Add secrets to GitHub

In your repository settings, go to Secrets and variablesActions.

Add the following secrets:

  • AWS_ACCESS_KEY_ID
  • AWS_SECRET_ACCESS_KEY

The workflow file

Create .github/workflows/deploy.yml:

name: Build and Deploy Blog

on:
  push:
    branches:
      - main

jobs:
  build_and_deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Install Nikola
        run: pip install 'nikola[extras]' watchdog aiohttp

      - name: Build blog
        run: nikola build

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: us-west-2

      - name: Deploy to S3
        run: |
          aws s3 sync output/ s3://your-bucket/ \
            --delete \
            --exclude "*.md" \
            --exclude ".git/*"

The --delete flag removes files from S3 that aren't in your build output (useful when renaming posts). The --exclude flags skip markdown source and git files.

A note on the official Nikola action

The getnikola/nikola-action is designed for GitHub Pages deployments (github_deploy), not S3. If you're using S3 (or another deployment method), a custom workflow like this is simpler and more transparent.

The action also had a subtle issue: running inside a Docker container, it couldn't find the repository due to git's safe.directory check (a security feature added in Git 2.36). The workaround was to create .gitconfig in the runner's home directory before the container ran, but at that point, using a direct Python install was cleaner. (See the related runner issue for details.)

Next steps

Deploy from the comfort of your editor. No manual builds, no SSH tunnels.

Comments

Comments powered by Disqus