PHP, Docker et heroku, cauchemar ou love story?

de Haut Alix
Darkmira FR
Published in
7 min readFeb 14, 2020

Heroku est une plateforme en tant que service qui a l’avantage d’offrir un plan gratuit idéal pour tester nos applications et gérer assez simplement un environnement Docker.

Pour en savoir un peu plus sur les avantages et limites d’Heroku je vous invite à lire cet article.

https://blog.derniercri.io/pourquoi-heroku-comme-hebergeur/

Il existe plusieurs façons de déployer sur Heroku.

Après un petit sondage rapide visant à connaître les préférences pour déployer PHP sur Heroku, deux en sont particulièrement ressorties :

https://twitter.com/tdutrion/status/1221900109195227143?s=20

Cet article visera donc à répondre à un de ces cas de figure particuliers : déployer une image Docker sur Heroku !

Suite à mon expérience récente, j’ai pu constater que peu d’exemples ou de tutoriels existent sur le sujet de façon complète. Mon but est de présenter un article pas à pas détaillé, regroupant toutes les particularités que j’ai pu rencontrer, afin de vous permettre un déploiement simple et réussi de votre environnement Docker!

C’est le premier article d’une série à destination tant des juniors que des expérimentés, chaque article comportera une section tl;dr pour les plus pressés.

Pour l’exemple, cet article est basé sur un projet Symfony 5 avec php 7.4 et Composer mais le principe restera le même pour tout autre projet.

Pour cela nous allons installer le skeleton Symfony de base avec

  • composer create-project symfony/skeleton

puis créer un controller dans le dossier /src/Controller du projet.

Son action sera très simple et affiche un message.

Le code du projet que l’on va déployer est disponible sur github à l’adresse suivante :

https://gitlab.com/Alixdehaut/my_symfony_app

L’application n’a donc que deux pages disponibles :

  • /
  • /me

Etape 1 : Docker

Prenons le Dockerfile à la racine du projet.

Les Dockerfiles sont des fichiers qui permettent de construire une image Docker adaptée à nos besoins, étape par étape.

Pour en savoir plus : https://docs.docker.com/engine/reference/builder/

On construit ici un Dockerfile multistage afin de build composer et php/apache.

Si vous ne connaissez pas les multistage build, je vous laisse lire la documentation officielle des docker multistage : https://docs.docker.com/develop/develop-images/multistage-build/

Voici le code complet de notre Dockerfile. https://gitlab.com/Alixdehaut/my_symfony_app/-/blob/master/Dockerfile

Le fichier bootstrap.php à été modifié de sorte à ce que les variables d’environnement ne soient pas définies à partir du .env mais à partir de l’environnement du container.

https://gitlab.com/Alixdehaut/my_symfony_app/-/blob/master/config/bootstrap.php

Par précaution, la variable d’environnement ‘ME’ est définie par défaut dans le fichier services .yaml.

https://gitlab.com/Alixdehaut/my_symfony_app/-/blob/master/config/services.yaml

Commençons par analyser l’étape Composer

Tout d’abord on définit l’image de base. Il est important pour la suite de donner un alias (as = alias) à notre étape car nous allons ainsi pouvoir y faire référence directement. En effet par défaut les étapes ne sont pas nommées et on y fait référence par leur numéro (la première instruction FROM = 0). Si les étapes viennent à être réorganisées, cela peut poser problème. Faire référence à un nom est donc plus pérenne.

WORKDIR permet de changer le répertoire courant de votre image, toutes les commandes qui suivront seront exécutées à partir de ce répertoire. Pour faciliter la rédaction des commandes et limiter la longueur des lignes, nous allons donc nous positionner dans le répertoire contenant notre application.

On copie ensuite les fichiers composer.json et composer.lock locaux dans notre image. Le dossier src/ doit également être ajouté à cette étape de sorte à générer l’auto chargement des classes optimisées.

Toujours dans notre image sera lancé composer install afin de récupérer et intégrer tous les composants utilisés dans notre projet, par convention, dans un dossier vendor.

Passons maintenant à la partie PHP.

Comme pour composer, on définit l’image de base puis le répertoire courant grâce aux variables d’environnement déclarées plus tôt.

On copie ensuite le contenu du projet ainsi que le dossier vendor généré dans notre étape Composer, d’où l’importance de la nommer plus haut!

On exécute ensuite plusieurs commandes dans notre image:

  • Ligne 12: Modifie le fichier de configuration de base par celui de production.
  • Ligne 13: Remonte les variables d’environnement du système dans apache. (https://www.php.net/manual/fr/ini.core.php#ini.variables-order)
  • Ligne 14: Active le mod_rewrite sur Apache pour activer les réécritures d’URLs.
  • Ligne 15: Change le propriétaire du PROJECT_DIR, actuellement ‘root’ par défaut dans docker, pour donner les accès à Apache (www-data).
  • Ligne 16: Change le dossier sur lequel va pointer le serveur apache.
  • Ligne 17: Définit le nom d’hôte. Par défaut, localhost est défini avec un DocumentRoot pointant sur /var/www. Il est ici précisé pour éviter d’avoir les erreurs apache dans l’output pour une configuration manquante.

Dernière étape très importante! (ligne 21)

Heroku a sa petite particularité et définit dynamiquement le port exposé sur le web à l’aide d’une variable d’environnement disponible au runtime.

Ainsi il faut remplacer le port défini dans la conf apache par la variable $PORT fournie par Heroku.

On test en local avec les commandes suivantes:

  • docker build . -t my-image-name
  • docker run -it — rm -e ME=yourName -p 8082:8082 my-image-name
  • dans le navigateur: http://localhost:8082

Etape 2: Déploiement sur Heroku

Installons Heroku CLI (qui va nous permettre d’interagir en ligne de commande avec les services proposés par la plateforme) :

https://devcenter.heroku.com/articles/heroku-cli

Ensuite, si vous n’avez pas de compte, il faudra vous en créer un

https://id.heroku.com/login

et s’y connecter.

  • heroku login

Commençons par créer une application sur https://dashboard.heroku.com.

Ou en ligne de commande:

  • heroku create YOUR-APP-NAME

Il faut ensuite se connecter à registry.heroku.com avec la commande suivante:

  • heroku container:login

Viens maintenant le moment de build et de push notre image vers le container registry:

  • heroku container:push web -a YOUR-APP-NAME

Attention! Il est important de nommer notre image ‘web’ car Heroku cherchera une image nommée web dont il se servira pour créer le container à exposer et faire tourner notre application.

Il est maintenant temps d’activer notre image sur l’app Heroku (comme un docker run):

  • heroku container:release web -a YOUR-APP-NAME

Il reste à définir notre variable d’environnement ME:

  • heroku config:set ME=YOUR_NAME -a YOUR-APP-NAME

Vous devriez maintenant voir sur l’interface d’heroku que le déploiement de l’image a correctement été fait.

Mais ce n’est pas tout à fait terminé.

Ouvrez votre application grâce à la ligne de commande

  • heroku open

ou via votre espace en cliquant sur le bouton ‘Open App’

Vous devriez tomber sur cette erreur!

Il ne nous reste qu’à consulter les logs avec la commande suivante:

  • heroku logs — tail -a YOUR-APP-NAME

En regardant de plus près on trouve l’erreur ci-dessous :

C’est la fameuse petite particularité des images docker qui contiennent Apache dont je vous parlais au début!

“Il semblerait qu’avant le démarrage du container, Heroku injecte des fichiers dans le répertoire /etc/apache2/.

L’image docker “php” active le module “mpm_prefork”, et crée ainsi le fichier /etc/apache2/mods-enabled/mpm_prefork.load.

Heroku injecte plus tard son propre ensemble de fichiers de configuration dont le fichier /etc/apache2/mods-enabled/mpm_event.load.

Ainsi, nous nous retrouvons avec 2 modules mpm chargés, ce qui fait échouer apache.”

https://github.com/docker-library/wordpress/issues/293#issuecomment-395526513

Pour résoudre cette erreur vous devez lancer dans votre terminal la commande suivante:

  • heroku labs:enable — app=YOUR-APP-NAME runtime-new-layer-extract

Faites de nouveau un push et release et en relançant votre application, tout devrait maintenant fonctionner correctement !

Voilà, vous avez déployé votre application sans trop d’embûches (enfin j’espère!).

Mais nous pourrions aller encore plus loin dans ce process.

Dans un soucis de déploiement continu, nous pourrions avoir envie d’automatiser ces tâches! Et si nous avons besoin d’une base de données ?

Ca tombe très bien ce sont les sujets de mes 2 prochains articles qui sont actuellement en préparation et disponibles bientôt !

tl;dr

Le projet est dispo à l’adresse https://gitlab.com/Alixdehaut/my_symfony_app, comprenant un Dockerfile avec les particularités expliquées ci dessus (voir Étape 1).

On crée l’app avec:

  • heroku create YOUR-APP-NAME

On se connecte:

  • heroku login
  • heroku container:login

Puis on lance (explication ci dessus voir Étape 2)

  • heroku labs:enable — app=YOUR-APP-NAME runtime-new-layer-extract

Il suffit maintenant de push et release notre image:

  • heroku container:push web -a YOUR-APP-NAME
  • heroku container:release web -a YOUR-APP-NAME

On définit notre variable d’environnement:

  • heroku config:set ME=YOUR_NAME -a YOUR-APP-NAME

Il ne reste qu’à ouvrir notre app:

  • heroku open

--

--