Déployer une application Phoenix avec Kamal & Docker sur un VPS

1. Introduction

Dans ce guide, je vais expliquer comment déployer une application Phoenix (Elixir) sur un VPS en utilisant Kamal, un outil de déploiement développé par l'équipe derrière Ruby on Rails. Kamal offre une façon simple et puissante de déployer des applications conteneurisées via SSH avec Docker.

2. Prérequis

  • Une application à déployer
  • Une Dockerfile pour l'app
  • Un VPS (Virtual Private Server ou serveur dédié virtuel) avec une connexion SSH (e.g. Hetzner (DE), OVH (FR), Infomaniak (CH)...)
  • Un nom de domain managé par un provider comme Cloudflare
  • Ruby >= 3.1 (pour Kamal)

3. Préparer votre application

Une fois qu'une base d'application fonctionnelle a été créée (dans le cas d'une application Elixir / Phoenix, un simple `mix phx.new my_app` suffira), elle doit être conteneurisée. Kamal aura besoin d'un conteneur à déployer sur le VPS. Ce conteneur peut ne pas être hébergé sur Docker Hub, mais dans mon cas, il l'est. Par défaut, Phoenix propose un fichier Docker, qui peut être généré avec la commande `mix phx gen.release --docker`, ce qui peut faire l'affaire. Dans mon cas, j'ai préféré en créer un nouveau. Une application Elixir est facile à conteneuriser, j'en ai donc profité !

# Dockerfile
FROM ubuntu:noble

ARG DEBIAN_FRONTEND=noninteractive

# For Kamal deployment using --skip-push argument
LABEL service=portfolio

# Set environment variables for locale settings
ENV SHELL=/bin/bash
ENV LANG=en_US.UTF-8
ENV LANGUAGE=en_US:en
ENV LC_ALL=en_US.UTF-8
ENV PHX_SERVER=true

# Set the working directory inside the container
WORKDIR /app

# Change ownership of /app to the "nobody" user
# Update the package index, upgrade packages, install required dependencies,
# then clean up to reduce image size
# Uncomment the locale in locale.gen and generate it
RUN chown nobody /app && \
    apt-get update -y && \
    apt-get upgrade -y && \
    apt-get install -y bash libstdc++6 openssl libncurses6 locales ca-certificates && \
    apt-get clean && \
    rm -f /var/lib/apt/lists/*_* && \
    sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && \
    locale-gen

# Copy the Phoenix release into the image and set ownership to "nobody"
COPY --chown=nobody:root ./_build/prod/rel/portfolio/ ./

# Open Phoenix port (4000 by default)
EXPOSE 4000

# Run the container as an unprivileged user for better security
USER nobody

# Start the Phoenix server when the container starts
SHELL ["bash", "-c"]
# CMD ["/app/bin/server"]
CMD ["/app/bin/portfolio", "start"]

4. Installation de Kamal

Pour installer Kamal, suivez la documentation en utilisant RubyGems :

gem install kamal

C'est facile et direct !

5. Configurer Kamal

Utilisez Kamal pour initialiser votre configuration de déploiement :

kamal init --deploy-to production --app my_app

Modifiez les fichiers générés sous config/deploy pour qu'ils correspondent à vos paramètres d'application, de domaine et de VPS. Définissez les variables d'environnement, les ports et le nom de l'image Docker.

Voici comment j'ai configuré Kamal pour mon propre projet Phoenix déployé sur un VPS Hetzner en utilisant Docker et PostgreSQL.

5.1 Extrait de kamal.yml

service: my_app
image: user_name/my_app
servers:
  web:
    - 123.45.67.89
ssh:
  user: amvcc
env:
  clear:
    PHX_HOST: myapp.com
    PHX_SERVER: true
    DATABASE_URL: ecto://postgres:motdepasse@localhost/mon_app_prod
    SECRET_KEY_BASE: ...
  secret:
    - SECRET_KEY_BASE
proxy:
  ssl: true
  hosts:
    - thibaultsan.com
    - amvcc.thibaultsan.com
    - photo.thibaultsan.com
  # Proxy connects to your container on port 80 by default.
  app_port: 4000
  healthcheck:
    interval: 3
    path: /
    timeout: 10
registry:
  username:
    - KAMAL_REGISTRY_USERNAME
  password:
    - KAMAL_REGISTRY_PASSWORD
builder:
  context: .
  arch: amd64

5.2 Explications

Ligne Explication
service Le nom de votre application/service, cohérent avec votre image Docker et votre projet Elixir.
image Nom complet de l'image Docker, hébergée sur DockerHub dans ce cas.
servers.web.hosts Adresse IP publique de votre VPS.
user Utilisateur SSH sur le VPS, généralement 'deploy' ou 'root'.
env.clear Variables d'environnement qu'il est sécuriser de commit (p.e. nom de domaine).
env.secret Pointe vers un fichier .env secret (non commité) utilisé pour les données sensibles comme SECRET_KEY_BASE.
registry Identifiants de registre pour pousser des images (GitHub, DockerHub, etc.).
builder.remote Si nécessaire, construit l'image à distance pour correspondre à l'architecture de votre serveur (par exemple amd64).

5.3 Exemple d'un .env.secret

SECRET_KEY_BASE=super_long_secret_key
DATABASE_PASSWORD=motdepasse

Ce fichier doit être maintenu en dehors du contrôle de version. Kamal le lira et injectera les variables dans le conteneur.

6. Déploiement

Une fois configuré, déployez votre application avec :

kamal deploy

Kamal va construire l'image Docker, la pousser, se connecter à votre VPS, et déployer les conteneurs. Utilisez les commandes Kamal comme kamal env push, kamal restart

Pour l'instant, je n'ai pas pipelisé le déploiement. Pour éviter de reconstruire l'image et pousser l'image de construction directement dans le CI, Kamal offre ces options :

kamal deploy --skip-push --version=latest

7. Conclusion

Kamal est un excellent choix pour les développeurs Phoenix qui veulent une pipeline de déploiement rapide et fiable sans orchestration complexe. Combiné avec Docker et un VPS comme Hetzner, il offre un contrôle total sur votre stack.

L'utilisation de Kamal pour déployer une application Phoenix a considérablement simplifié le processus par rapport aux méthodes traditionnelles comme la configuration SSH manuelle, les playbooks Ansible, ou même certaines solutions PaaS.

Contrairement aux déploiements manuels qui nécessitent des étapes répétitives, ou à Ansible qui ajoute une couche de complexité, Kamal offre un outil de déploiement moderne basé sur Ruby qui est simple, transparent et natif à Docker.

Pourquoi Kamal ?

  • Configuration minimale : un seul fichier kamal.yml contrôle tout.
  • Docker-native : construit et déploie des conteneurs sans avoir besoin d'outils d'orchestration supplémentaires.
  • Support de construction à distance : utile lorsque votre architecture locale diffère du serveur (par exemple, M1 Mac vers VPS amd64).
  • Pas de dépendance à un fournisseur : fonctionne avec n'importe quel VPS ou fournisseur cloud.
  • Gestion sécurisée des secrets : gère facilement les fichiers .env privés.
  • Retour en arrière et redéploiement faciles avec des commandes simples comme `kamal deploy` ou `kamal rollback`.

Comparé à des solutions comme Fly.io, qui font abstraction de l'infrastructure, Kamal vous donne un contrôle total tout en gardant le flux de travail efficace et reproductible.

Dans l'ensemble, Kamal comble le fossé entre la gestion manuelle des serveurs et les plateformes entièrement gérées, offrant un excellent équilibre entre simplicité, puissance et transparence.

Si vous recherchez une solution de déploiement autonome et simple pour les applications Elixir Phoenix, Kamal est un choix fantastique.