Base Project
Esta na hora de partir para uma pipeline completa e para isso precisaremos de um código que possamos aplicar as várias steps de uma pipeline. Começaremos com o CI inicialmente e o CD podemos implementar no final utilizando GitOps.
Faça um fork desse repositório para a sua conta e vamos começar. Lembrando, esse código não é meu, mas podemos usar aplicar na prática as pipelines e usar os recurso do gitlab-ci. Lembre-se de fazer o fork para dentro do grupo que possui o runner que você criou se quiser usá-lo, caso contrário usará os runners shared do gitlab.
Ao clonar esse repositório podemos observar que é um projeto feito em node, ou seja, para rodar vamos precisar do nodejs.
~/Desktop/gitlab
❯ git clone [email protected]:puziol/learn-gitlab-app.git
❯ cd learn-gitlab-app
# O que temos no projecto
❯ tree -L 1
.
├── e2e
├── eslint.config.js
├── index.html
├── node_modules
├── package-lock.json
├── package.json
├── playwright.config.cjs
├── public
├── README.md
├── src
├── tests
└── vite.config.js
Vamos fazer uma modificação para que tudo seja instalado automaticamente e de forma fácil.
Instale o DevBox e entenda como funciona, é importante pois acredito que mudará a sua vida daqui pra frente.
Já que é um projeto em nodejs precisamos instalar o Node para rodar. Isso é a primeira coisa que devemos saber, pois em uma pipeline vamos precisar no processo de build proporcionar o ambiente que possa buildar esse código.
- Instalar as dependências: Vamos precisar do npm para gerenciamento de pacote e isso vem com a instalação do nodejs.
- Buildar o projeto
- Testar
Na pasta do projeto vamos instalar inicializar o devbox.
# Inicializando o projeto com o devbox
❯ devbox init
# Adicionando o nodejs na versão que queremos
❯ devbox add [email protected]
# Ao iniciarmos o projeto dentro de um ambiente específico teremos tudo que precisamos sem instalar nada na máquina. Também podemos aproveitar isso para o pipeline futuramente.
❯ devbox shell
❯ node --version
v23.11.0
❯ npm --version
10.9.2
Irá observar que uma pasta .devbox aparecerá então é bom colocá-la no git-ignore para não subir para o repositório e também o devbox.lock
❯ echo ".devbox" >> .gitignore
❯ echo "devbox.lock" >> .gitignore
Agora que já estamos em um ambiente isolado, podemos instalar os as dependências antes de buildar o projeto e para isso podemos fazer.
❯ npm install
added 371 packages, and audited 372 packages in 3s
123 packages are looking for funding
run `npm fund` for details
6 vulnerabilities (4 moderate, 2 critical)
To address all issues, run:
npm audit fix
Run `npm audit` for details.
Aqui já temos um ponto importante para a pipeline que vamos montar, conferir as vulnerabilidades de pacotes.
Vamos acertar isso fazendo o update dos pacotes.
❯ npm update
added 11 packages, removed 2 packages, changed 85 packages, and audited 380 packages in 17s
130 packages are looking for funding
run `npm fund` for details
6 moderate severity vulnerabilities
To address all issues (including breaking changes), run:
npm audit fix --force
Run `npm audit` for details.
####
❯ npm audit fix --force
npm warn using --force Recommended protections disabled.
npm warn audit Updating vitest to 3.1.2, which is a SemVer major change.
npm warn audit Updating @vitest/ui to 3.1.2, which is a SemVer major change.
added 3 packages, removed 7 packages, changed 11 packages, and audited 376 packages in 2s
129 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
Pronto agora não temos nenhuma vulnerabilidade e temos a pasta node_modules com as dependências necessárias (pacotes). Agora podemos buildar o projeto.
❯ npm run build
> [email protected] build
> vite build
vite v6.3.2 building for production...
✓ 31 modules transformed.
build/index.html 0.47 kB │ gzip: 0.30 kB
build/assets/react-CHdo91hT.svg 4.13 kB │ gzip: 2.14 kB
build/assets/index-n_ryQ3BS.css 1.39 kB │ gzip: 0.72 kB
build/assets/index-BcKvuBhg.js 147.40 kB │ gzip: 47.58 kB
✓ built in 338ms
E temos a pasta build que também será ignorada pelo .gitignore ao subir o código. É isso que precisamos ter no processo de build, nossa pasta build.
Vamos adicionar o pacote serve
para rodar o projeto e verificar?
❯ npm install serve
❯ npx serve ./build
┌────────────────────────────────────────┐
│ │
│ Serving! │
│ │
│ - Local: http://localhost:3000 │
│ - Network: http://10.0.0.26:3000 │
│ │
│ Copied local address to clipboard! │
│ │
E temos o projeto rodando no navegador.
Dois pontos importantes ao aproveitar o devbox. O npm install poderia fazer parte da inicialização quando o devbox subir o que acha?
Vamos editar o arquivo do devbox para quando ele inicializar já fazer npm install se existir o arquivo package.json. Podemos remover o test, pois não usaremos agora.
{
"$schema": "https://raw.githubusercontent.com/jetify-com/devbox/0.14.2/.schema/devbox.schema.json",
"packages": ["[email protected]"],
"shell": {
"init_hook": [
"if [ -f package.json ]; then npm install; fi"
]
}
}
O código pronto está na branch devbox.
Quero deixar claro que poderíamos instalar o node na máquina e não precisaríamos do devbox, mas a limpeza e a facilidade que podemos ter no futuro com as pipelines usando o devbox é de outro mundo, veremos adiante.
Não para esse estudo, mas a pipeline podería começar localmente! Na verdade muitos erros que teremos com a pipeline no futuro poderiam ser resolvidos localmente antes de subir o código. Veja um pouco sobre o pre-commit mas não implemente para esse estudo.
Agora falando um pouco da pipeline, o runner tem que esta pronto para executar esse projeto e temos 3 opções.
1 - Instalar o node assim que o runner começar. Se vamos começar a partir da imagem "debian:bullseye-slim", saiba que ela não tem nodejs então seria necessário instalar através do gerenciador de pacotes apt desta imagem. 2 - Utilizar o mesmo processo que fizemos localmente para rodar, ou seja, instalar o devbox e deixar que ele prepare o ambiente completo. Seria a mesma coisa que o item 1, porém utilizariamos menos comandos. 3 - Utilizar uma imagem docker que já vem com o node instalado ao invés do "debian:bullseye-slim".
Todo o processo de instalação demanda tempo até que o runner fique pronto para fazer o que precisa ser feito. Cada job executará todo esse processo, de preparar o runner para que ele tenha capacidade de fazer o que precisa.
A melhor forma de fazer isso é utilizar o item 3, rodando uma imagem que já temos o que precisamos, porém nem sempre temos tudo que precisamos em uma imagem e as vezes uma imagem é tão específica e para ficar pequena tudo é removido. Por exemplo um imagem node pode não ter o gerenciador de pacote instalado para que instemos um Curl caso precisarmos.
A melhor forma é criar uma imagem com tudo o que precisamos, mas para isso é necessário conhecimento em Docker. Se você não tem essa expertise deveria ter, pois hojé é base para DevOps e desenvolvimento.
Dicas
Ao criar um projeto podemos definir algumas coisas no Settings. Podemos fazer isso em nível de grupo também para toda a organização. O que é bom fazer?
Em Settings > Merge request
temos algumas opções.
Qual o tipo de método de merge?
A opção acima não cria um commit para o merge request mantendo o histório mais limpo.
Squashing commits é útil ao fazer o push. Se você fez várias modificações na branch, ao invés de mesclar todas as alterações individualmente feita na sua branch, ela irá criar um único commit na branch destino. É muito melhor para ler o histórico.
É interessante sempre que o pipeline tenha sucesso e todas as marcações de revisão tenha sido corrigidas.
Outra coisa que podemos fazer é criar branchs protegidas, para que ninguém possa fazer um push direto para uma branch como a main, sendo necessário um merge request e forçando a execução da pipeline. A brach main já é protegida, mas pessoas podem fazer push direto para ela. Podemos eliminar isso.