Ferramentas

Criando sites estáticos com Jekyll e Podman

  • containers
  • documentação
  • jekyll
  • podman
Última atualização: 2024-01-02

Resumo

Ao desenvolver um site estático com o Jekyll, um dos principais problemas é recriar o ambiente de desenvolvimento para testar o site localmente, devido a diferenças de versões do Ruby, gems instaladas, bibliotecas e ambientes de desenvolvimento disponíveis. Uma boa alternativa é utilizar containers para criar um ambiente semelhante ao do GitHub Pages que permite testar as alterações localmente, antes de publicar a página. Neste documento é descrita uma forma de facilitar esse processo, de forma que seja repetível, mesmo em diferentes ambientes de desenvolvimento, utilizando o podman para executar os containers.

Posts Relacionados

Um dos principais problemas ao desenvolver páginas para o GitHub Pages, utilizando o Jekyll, é testar as alterações nas paginas, antes de publicá-las, uma vez que, até hoje, não existe uma área de “espera” (staging area), onde é possível visualizar as páginas sem publicá-las. Uma solução para esse prbolema é executar um container com podman, criando, na máquina de desenvolvimento, um ambiente semelhante ao oferecido pelo GitHub.

O Jekyll é um sistema de geração de sites estáticos, com suporte a blog, implementado em Ruby, e que permite uma série de customizações com relação a plugins para modificar o comportamento ou a forma como as páginas são geradas. Os plugins são gems Ruby, e alguns destes plugins requerem ferramentas de desenvolvimento local (como o toolchain GCC) para a sua compilação e instalação.

A diferença de versões de Ruby, ferramentas de desenvolvimento, ou gems instaladas na máquina de desenvolvimento, dificultam a criação do ambiente para gerar e testar, localmente, as páginas editadas, apesar da ferramenta bundle, a qual já facilita muito esse processo.

Utilizando containers

Uma forma de obter um ambiente estável e reproduzível, mesmo em plataformas diferentes, é o uso de containers, popularizados a partir do Docker.

Containers fornecem uma forma de criar um ambiente isolado sobre o sistema operacional, isolando as camadas de software necessária para um ambiente específico. Esse isolamento de container, permite limitar o uso de CPU, armazenamento, e memória, além de isolar todo o stack de software utilizado do sistema hospedeiro.

Definimos o ambiente do container a partir de um arquivo (Dockerfile), onde descrevemos todos os recursos de hardware e software que serão utilizados. É a partir deste arquivo de descrição que será criada uma imagem para o container.

Toda imagem é, normalmente, criada a partir de uma imagem pré-existente. Selecionamos a imagem base a partir da diretiva FROM. Para criar uma imagem que nos permita simular o ambiente do GitHub Pages, executando o Jekyll, utilizamos uma imagem oficial da liguagem de programação Ruby.

FROM ruby:alpine

Precisaremos de um toolchain GCC para compilar alguns módulos (gem) que não são 100% Ruby, e para isso, executamos o comando apk, que é o gerenciador de pacotes do Alpine Linux, utilizando a diretiva RUN.

RUN apk add --no-cache make gcc g++ libc-dev openssl-dev

Ao executar o container precisamos instalar as gems utilizadas pelo site, e iniciar o servidor do Jekyll. Como esse processo envolve dois programas em execução, definimos o ENTRYPOINT como:

ENTRYPOINT ["/bin/sh", "-c", \
    "bundle install && \
     bundle exec jekyll server --trace --host 0.0.0.0" \
]

O servidor do Jekyll executa na porta 4000 (TCP), então temos que expor essa porta do container:

EXPOSE 4000

Ainda precisamos nos preocupar com um ponto muito importante: como o Jekyll irá acessar o codígo fonte (Markdown) do site?

Para isso configuramos um VOLUME, que permitirá que um diretório no host seja compartilhado com o container:

VOLUME ["/srv/jekyll"]

O mapeamento do diretório compartilhado será feito na chamada de execução do container.

Um último problema a ser solucionado é que uma vez criada a imagem, todas as alterações da imagem feitas pelo bundle install são perdidas, quando o container termina a execução, e a instalação do bundle deve ser refeita sempre que o container inicializar, incluindo acessar os repositórios remotos de código para as gems.

Para evitar isso, podemos configuar a imagem para utilizar um diretório específico para armazenar as gems e todos os plugins utilizados pelo Jekyll, e associar esse diretório com um diretório do host, criando um cache para os arquivos.

Essa configuração envolve uma série de variáveis de ambiente, que são definidas com a diretiva ENV.

Abaixo encontra-se o arquivo Dockerfile completo para a criação do container:

FROM ruby:alpine

RUN apk add --no-cache make gcc g++ libc-dev openssl-dev

VOLUME ["/srv/jekyll"]

EXPOSE 4000

ENV GEM_HOME="/usr/local/bundle"
ENV BUNDLE_PATH=/usr/local/bundle \
    BUNDLE_SILENCE_ROOT_WARNING=1 \
    BUNDLE_APP_CONFIG=/usr/local/bundle
ENV PATH=/usr/local/bundle/bin:/usr/local/bundle/gems/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

RUN mkdir -p "/usr/local/bundle"

WORKDIR "/srv/jekyll"
ENTRYPOINT ["/bin/sh", "-c", \
    "bundle install && \
     bundle exec jekyll server --trace --host 0.0.0.0" \
]

Agora podemos criar a imagem para o container, e associar a ela um TAG (_my_user_name/site_server_):

podman build -t "my_user_name/site_server" /path/to/Dockerfile/directory

Uma vez que a imagem é criada, podemos inicir um container, definindo o diretório com os arquivos fontes do site, e o diretório que será utilizado como cache (lembre-se de criar o diretório antes, caso ele não exista):

podman run \
    --volume "/path/to/website:/srv/jekyll:Z" \
    --volume "/path/to/website/.vendor/bundle:/usr/local/bundle:Z" \
    -p 4000:4000 \
    "my_user_name/site_server"

Agora, com o container executando, é possivel acessar o site gerado pelo Jekyll no endereço http://localhost:4000.

Configurando os plugins

Agora que temos um ambiente reproduzível, precisamos configurar os plugins de forma a simular o ambiente do GitHub Pages.

As versões utilizadas pelo GitHub são públicas e basta que as versões definidas no Gemfile limitem as versões das gems a essas versões disponíveis no Github.

Abaixo, está a contfiguração utilizada nesse site, na data que esse post foi escrito:

source "https://rubygems.org"
gem "jekyll", "~> 3.9.3"
group :jekyll_plugins do
  gem "jekyll-feed", "~> 0.12"
end

gem "kramdown", "~> 2.3.2"
gem "kramdown-parser-gfm"
gem "webrick", "~> 1.7"
gem "ffi", "~> 1.15.5"

Conclusão

Manter uma configuração para utilizar o Jekyll em diferentes ambientes e diferentes sistemas operacionais pode ser muito trabalhoso, principalmente por causa das versões e dependências da linguagem Ruby e das gems utilizadas pelo Jekyll.

Utilizando containers, podemos criar um ambiente que pode ser reproduzido com certa facilidade, mesmo em sistemas operacionais diferentes, e mantendo a compatibilidade com o GitHub Pages, de forma a permitir a visualização das páginas editadas antes da sua publicação.

Bônus: utilizando um script para iniciar o Jekyll

Na elaboração deste site, eu utilizo um script que me permite criar a imagem do container apenas se necessário, e iniciar o container sem me preocupar com a configuração necessária na linha de comando, permitindo que o processo seja apenas copiar o repositório e executar o script.

Abaixo você encontra esse script, que foi testado apenas no Linux, mas deve funcionar em qualquer shell razoavelmente compatível com o Bash (como o zsh do macOS).

Para executar o script, basta que ele esteja no mesmo diretório do Dockerfile.

#!/bin/sh

SCRIPTDIR="$(realpath $(dirname "$0"))"
TOPDIR="$(dirname "${SCRIPTDIR}")"

TAG="$(whoami)/site_server"

[ -d "${TOPDIR}/.vendor/bundle" ] || mkdir -p "${TOPDIR}/.vendor/bundle"


existing=$(podman images -f reference="localhost/${TAG}" --format "{{ .Repository }}")

[ -z "${existing}" ] && podman build -t "${TAG}" "${SCRIPTDIR}"

podman run \
    --volume "${TOPDIR}:/srv/jekyll:Z" \
    --volume "${TOPDIR}/.vendor/bundle:/usr/local/bundle:Z" \
    -p 4000:4000 \
    "${TAG}"