Continuous Integration for a Phoenix LiveView Site (Without Postgres)
Dans le cadre de la construction et du maintien d'un site LiveView fiable, un pipeline CI solide est essentiel. Ci-dessous, je vais vous guider à travers la configuration CI que nous utilisons pour ce site, qui est construit avec Phoenix (Elixir), et déployé en utilisant Kamal (un autre article sera écrit à ce sujet !) et Docker. Au moment où nous écrivons ces lignes, l'application ne repose pas sur une base de données comme Postgres, mais nous avons laissé le support commenté pour une expansion future.
We use GitHub Actions pour CI, ce qui nous donne des environnements propres et isolés et une bonne intégration avec Docker Hub et les versions versionnées.
1. Triggers
on:
push:
branches: [main]
tags:
- "*"
pull_request:
branches: [main]
The CI pipeline runs automatically:
-
On any push to the
mainbranche - When a new tag is pushed (used for Docker image release)
-
On pull requests targeting
main
2. What is Mise?
Mise est un gestionnaire de versions moderne qui prend en charge plusieurs langages et outils — similaire à asdf, mais plus rapide et plus facile à configurer. En utilisant un seul fichier de configuration (`mise.toml`), vous pouvez définir les versions exactes d'outils comme Elixir, Erlang, Ruby, et même des environnements JavaScript comme Bun.
En développement local comme en CI, cela garantit que tout le monde — et chaque tâche — utilise les mêmes versions. No more surprises between your machine and GitHub Actions!
2.1 Defined Tool Versions
[tools]
bun = "1.2.11"
elixir = "1.18.3"
erlang = "27.3.4"
ruby = "3.4.2"
This setup guarantees consistent runtime environments:
-
bunfor asset management -
elixiranderlangfor the Phoenix backend -
rubyfor tools like Kamal used in deployment
2.2 Reusable Tasks
Mise vous permet également de définir des tâches CLI, vous pouvez donc standardiser les flux de travail courants comme la configuration, les nettoyages et les assistants de déploiement. These tasks are great for onboarding and automation.
[tasks.reset]
description = "Reset and recompile project"
run = "rm -rf deps _build .elixir_ls && mix deps.get && mix && mix test"
[tasks."clean:images"]
description = "Delete images folders"
run = "rm -rf priv/waffle"
[tasks."setup:kamal"]
description = "Install kamal to manage deployment"
run = "gem install kamal"
[tasks."assets:install"]
description = "Install JS assets"
run = "bun install --cwd assets"
2.3 Mise in CI
In the CI, we use the official `jdx/mise-action` to install and cache these tools quickly. Cela accélère les builds, évite les versions incompatibles et maintient des environnements CI miroirs des environnements de développement locaux.
En mettant en cache les artefacts de mixage et de construction par version d'outil et par environnement, nous gagnons également du temps sur les exécutions répétées de CI :
key: ${{ env.MIX_ENV }}-mise-${{ hashFiles('**/mise.toml') }}-mix-${{ hashFiles('**/mix.lock') }}
3. Job Overview
The workflow defines four jobs. Let's dig into these !
3.1 Tools
This job initializes the CI environment by installing all required tools using
mise. Il assure la cohérence entre le développement et la CI en alignant les versions des outils définies dans
mise.toml.
tools:
name: TOOLS
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up tools
uses: jdx/mise-action@v2
3.2 Lint
Le job LINT applique les règles de qualité de code et de sécurité. Il vérifie le formatage, les dépendances inutilisées, les vulnérabilités de sécurité et les problèmes stylistiques.
Tools like
credo, sobelow, hex.audit, et
mix deps.unlock --check-unused
are used to ensure the codebase stays clean and secure.
lint:
name: LINT
needs: [tools]
runs-on: ubuntu-24.04
env:
MIX_ENV: dev
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up tools
uses: jdx/mise-action@v2
- name: Cache
uses: actions/cache@v4
with:
path: |
deps
_build
key: ${{ env.MIX_ENV }}-mise-${{ hashFiles('**/mise.toml') }}-mix-${{ hashFiles('**/mix.lock') }}
restore-keys: |
${{ env.MIX_ENV }}-mise-${{ hashFiles('**/mise.toml') }}-mix-
${{ env.MIX_ENV }}-mise-
- name: Install dependencies
run: mix deps.get
- name: Compile without warnings
run: mix compile --warnings-as-errors
- name: Check code format
run: mix format --dry-run --check-formatted
- name: Check unused deps
run: mix deps.unlock --check-unused
- name: Hex audit
run: mix hex.audit
- name: Deps audit
run: mix deps.audit
- name: Run credo
run: mix credo
- name: Run sobelow
run: mix sobelow --config
3.3 Test
This job runs the test suite with
mix test. Il garantit que l'application se comporte comme prévu.
PostgreSQL est commenté pour l'instant car le projet ne nécessite pas actuellement de base de données, mais la configuration est prête pour une utilisation future.
test:
name: TEST
needs: [tools]
runs-on: ubuntu-24.04
env:
MIX_ENV: test
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up tools
uses: jdx/mise-action@v2
- name: Cache
uses: actions/cache@v4
with:
path: |
deps
_build
key: ${{ env.MIX_ENV }}-mise-${{ hashFiles('**/mise.toml') }}-mix-${{ hashFiles('**/mix.lock') }}
restore-keys: |
${{ env.MIX_ENV }}-mise-${{ hashFiles('**/mise.toml') }}-mix-
${{ env.MIX_ENV }}-mise-
- name: Install dependencies
run: mix deps.get
- name: Run tests
run: mix test --max-failures=1 --color
3.4 Build
Le job BUILD compile l'application, construit les assets et génère une release. Il utilise les paramètres de production pour s'assurer que la sortie compilée correspond à ce qui sera déployé. Si un tag Git est poussé, il construit également et pousse une image Docker vers Docker Hub en utilisant les secrets configurés.
build:
name: BUILD
needs: [lint, test]
runs-on: ubuntu-24.04
env:
MIX_ENV: prod
PHX_SERVER: true
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up tools
uses: jdx/mise-action@v2
with:
cache_key_prefix: mise-${{ runner.os }}
- name: Cache
uses: actions/cache@v4
with:
path: |
deps
_build
key: ${{ runner.os }}-${{ env.MIX_ENV }}-mise-${{ hashFiles('**/mise.toml') }}-mix-${{ hashFiles('**/mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.MIX_ENV }}-mise-${{ hashFiles('**/mise.toml') }}-mix-
${{ runner.os }}-${{ env.MIX_ENV }}-mise-
- name: Install web dependencies
run: mise run assets:install
- name: Install backend dependencies
run: mix deps.get --only prod
- name: Build
run: MIX_ENV=${{ env.MIX_ENV }} mix do assets.setup, assets.deploy, compile --warnings-as-errors, release --overwrite
- name: Docker meta
if: ${{ contains(github.ref, 'refs/tags/') }}
uses: docker/metadata-action@v5
id: meta
with:
images: ${{ env.DOCKER_IMAGE }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
- name: Set up docker buildx
if: ${{ contains(github.ref, 'refs/tags/') }}
id: buildx
uses: docker/setup-buildx-action@v3
- name: Login to docker hub
if: ${{ contains(github.ref, 'refs/tags/') }}
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Release
if: ${{ contains(github.ref, 'refs/tags/') }}
uses: docker/build-push-action@v6
with:
context: .
push: true
cache-from: type=gha
cache-to: type=gha,mode=max
builder: ${{ steps.buildx.outputs.name }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
4. Docker Image Release
On each tagged commit, the workflow:
- Builds the Phoenix release with static assets
- Generates Docker metadata and tags
- Logs into Docker Hub using secrets
- Builds and pushes the image using build-push-action
5. Conclusion
This setup gives us confidence in code quality and makes production releases seamless Si vous construisez un site Phoenix LiveView sans base de données, c'est une bonne base. When you're ready to enable Postgres, the foundation is already there.