Migration de notre stack de développement vers Docker
tip
Il existe une version de cet article moins technique et plus orientée développeurs macOS écrite par Tristan Bessoussa, voir Un environnement de développement sain en 2021. Bonjour Docker, bye machines virtuelles.
Chez yProximité, agence web, on utilise les machines virtuelles depuis plusieurs années comme stack de développement sur tous nos différents projets web, afin de mettre en place un environnement de développement complet avec un serveur web nginx, PHP, Node.js, une base de données et Redis.
Pour cela, on utilise :
- make, car les
Makefile
c'est super pratique - VirtualBox pour la virtualisation des machines
- Vagrant pour les contrôler
- Ansible pour automatiser le provisionnement
- les rôles Ansible de Manala, développés par l'agence web Elao et qui nous prémâche 95% du travail (un grand merci ❤️)
Ça fonctionne plutôt bien (quand ça fonctionne :stuck_out_tongue_winking_eye:), et ça a pour avantage d'avoir un environnement de développement totalement fonctionnel et isolé de la machine hôte.
En revanche, plusieurs années ont passé et l'équipe et moi-même avons rencontré plusieurs problèmes.
Les très nombreux problèmes avec les machines virtuelles
Problèmes systèmes
- Pour les utilisateurs d'Ubuntu 18.04 ou plus, le plugin Vagrant Landrush (qui permet de mapper un faux NDD vers l'IP d'une
VM) ne fonctionne pas. On doit manuellement ajouter une entrée dans notre
/etc/hosts
. - Il y a des versions de VirtualBox qui ne fonctionnent pas. Dès que je trouvais une version qui fonctionnait parfaitement (ex :
6.1.16
pour Ubuntu 20.10), je désactivais les mises à jour viaecho "virtualbox-6.1 hold" | sudo dpkg --set-selections
, dans la crainte qu'une mise à jour ne fasse plus fonctionner les VM. - Des soucis d'optimisation d'espace disque. Avec une VM par projet, l'espace disque utilisé peut monter assez vite (~160 Go utilisé après 3 ans sans nettoyage).
- Des problèmes de consommation CPU / RAM, faire tourner une ou plusieurs VM, avec PhpStorm, avec Google Chrome, etc... le tout en même temps, ce n'est pas donné à tout le monde. Il faut avoir une très bonne machine capable d'encaisser la charge.
- Il y a des fichiers importants qu'il faut importer dans la VM (
~/.ssh/config
,~/.composer/auth.json
,~/.gitconfig
, ...), ça se fait automatiquement au boot de la VM, mais bon... - Les machines virtuelles ne seront pas utilisables sur les nouveaux Mac tournant sour CPU ARM, Tristan Bessoussa en parle très bien sur son article.
Problèmes applicatifs
- Le watching de fichiers (le fait d'observer les modifications sur des fichiers en temps réel) qui ne fonctionne pas correctement à cause de l'utilisation de NFS :
- pour faire fonctionner le dev-server de webpack, on a dû configurer le polling
- pour faire fonctionner le watch de TailwindCSS, on a également dû configurer le polling via
CHOKIDAR_USEPOLLING=1
(discussion GitHub)
- Pour nos git hooks ou tests Cypress (tout ce qui peut être lancé dans et en dehors de la VM en fait), il fallait passer par un petit script
vagrant-wrapper.sh
pour s'assurer que nos commandes soient bien exécutées dans la VM :
1#!/usr/bin/env bash 2 3# Permet d'exécuter une commande dans la VM, que l'on soit dans la VM ou non. 4# Exemple : ./vagrant-wrapper.sh bin/phpstan analyse 5 6vagrant_wrapper() { 7 local user_command=$@ 8 9 # we assume that we are outside the VM if command `vagrant` is available 10 if [[ -x "$(command -v vagrant)" ]]; then 11 vagrant ssh -- "cd /srv/app && ${user_command}" 12 else 13 eval ${user_command} 14 fi 15} 16 17vagrant_wrapper $@
- Si on utilisait un certificat HTTPS local (ex : généré avec mkcert), il fallait relancer nginx après que la partition NFS (contenant notre certificat HTTPS) soit montée, sinon nginx ne se lançait pas car le certificat HTTPS était introuvable :
1Vagrant.configure(2) do |config| 2 # ... 3 config.trigger.after :up do |trigger| 4 trigger.name = "nginx" 5 trigger.info = "Starting nginx..." 6 trigger.run_remote = {inline: "if systemctl cat nginx >/dev/null 2>&1; then sudo systemctl start nginx; fi"} 7 end 8end
- Si l'un de nos projets dépend d'un autre (ex : projet A qui utilise l'API du projet B et qui est en HTTPS), alors il fallait importer le certificat racine de mkcert dans la VM (cette solution n'a été testée que sous Debian/Ubuntu, ne fonctionne sans doute pas sous macOS) :
1Vagrant.configure(2) do |config| 2 # ... 3 Dir['/usr/local/share/ca-certificates/mkcert_*'].each do |path| 4 filename = path.split('/').last 5 6 config.vm.provision 'file', run: 'always' do |file| 7 file.source = path 8 file.destination = "/home/#{config.ssh.username}/#{filename}" # file provisionner can't write in /usr/local/... due to permissions, we have to use a trigger 9 end 10 11 # copy to /usr/local/..., apply permissions and update CA certificates 12 config.trigger.after [:up, :provision] do |trigger| 13 trigger.name = "mkcert" 14 trigger.info = "Copying mkcert's CA file..." 15 trigger.run_remote = { 16 inline: 'if [ -f "%{source}" ]; then mv "%{source}" "%{path}" && chown root:staff "%{path}" && update-ca-certificates; fi' % { source: "/home/#{config.ssh.username}/#{filename}", path: path } 17 } 18 end 19 end 20end
Des performances en retrait
- L'installation initiale (via le provisioning Ansible) est très longue, environ 15 minutes, tellement il y a de choses à installer et à configurer.
- La VM peut prendre plusieurs dizaines de secondes à boot.
- La partition NFS n'aide pas du tout, même si on a déjà réussi à diviser par 4 le temps des
yarn install
etcomposer install
en utilisant cette configuration :
1Vagrant.configure(2) do |config| 2 # ... 3 config.vm.synced_folder '.', '/srv/app', 4 type: 'nfs', 5 mount_options: ['vers=3', 'tcp', 'rw', 'nolock', 'actimeo=1'], 6 linux__nfs_options: ['rw', 'all_squash', 'async'] 7end
tl;dr
Trop de problèmes de lenteur, trop de problèmes nécessitant des tweaks et du temps investi pour les outrepasser...
Analyses et réflexions
Pour récapituler :
- nos projets webs nécessitent un serveur web, Nginx
- ils sont tous basés sur du PHP (mais avec des versions différentes)
- certains ont besoin de Node.js (également avec des versions différentes) pour build des assets via webpack Encore
- certains ont besoin d'une base de données MySQL, MariaDB ou PostgreSQL (encore avec des versions différentes aussi)
- certains ont besoin de Redis
Réflexions
Je sais par expérience que :
- il est facile d'installer nginx, mais ce n'est peut-être pas forcément l'idéal... à voir comment gérer les noms de domaines
- il est très facile d'installer plusieurs versions de PHP, via :
- le PPA d'Ondrej pour Ubuntu,
- le DPA Sury.org/PHP pour Debian,
- Brew pour macOS et Linux,
- ou encore phpenv pour macOS et Linux également
- il est également très facile d'installer plusieurs version de Node.js, via :
- installer des serveurs de base de données et gérer différentes versions en même temps, c'est UNE HORREUR et je n'ai pas envie de bousiller ma machine
- installer Redis globalement n'est peut-être pas la meilleure des idées non plus...
Avec ce constat, je me suis dit qu'on pouvait tenter une stack hybride :
- PHP et Node.js installés sur la machine
- les bases de données et Redis installés via Docker
- il ne manque plus que le serveur web... Comment fait-on ?
Symfony CLI ✨
Symfony CLI est un outil écrit en Go et qui a notamment remplacé l'ancien Symfony WebServerBundle. On l'utilise déjà sur nos CI pour lancer un serveur web pour nos tests E2E avec Cypress.
Mais ce n'est pas tout. Symfony CLI est un outil surboosté aux vitamines avec d'incroyables fonctionnalités permettant de régler plusieurs problématiques :
- on peut donc de démarrer un serveur web, bye Nginx 👋
- on peut activer le support d'HTTPS, bye mkcert 👋
- on peut définir des noms de domaines grâce à un proxy local, bye Landrush 👋
- on peut configurer PHP via un
php.ini
, super pratique pour configurer la timezone ou activer/désactiver XDebug, - on peut configurer la version de PHP à utiliser via un fichier
.php-version
tip
Ça signifie qu'il faut utiliser le binaire symfony
pour exécuter des commandes/binaires avec la bonne version de PHP :
- PHP via
symfony php
(ex :symfony php bin/phpstan analyze
) - Composer via
symfony composer
(ex :symfony composer install
) - La console Symfony avec le raccourci
symfony console
(ex :symfony console cache:clear
)
- et le plus exceptionnel, une intégration avec Docker qui permet de définir automatiquement des variables
d'environnement en fonction des containers. Si on utilise un container pour une base de données, alors
DATABASE_URL
sera automatiquement définie et c'est PARFAIT pour nous ! :heart_eyes:
warning
Le binaire symfony
utilisera toujours les variables d'environnement détectées via Docker et ignorera les variables d'environnement locales.
Cela veut dire que les variables d'environnement DATABASE_URL
, REDIS_URL
, etc... définies dans vos .env
ou .env.local
ne seront pas utilisées.
Docker
On a donc utilisé des containers Docker pour la base de données et Redis, exemple :
1version: '3.6' 2 3volumes: 4 db-data: 5 redis-data: 6 7services: 8 database: 9 image: 'postgres:12-alpine' 10 ports: [5432] 11 environment: 12 POSTGRES_USER: 'app' 13 POSTGRES_PASSWORD: 'app' 14 POSTGRES_DB: 'app' 15 TZ: Etc/UTC 16 PGTZ: Etc/UTC 17 volumes: 18 - db-data:/var/lib/postgresql/data 19 healthcheck: 20 test: pg_isready 21 interval: 10s 22 timeout: 5s 23 retries: 5 24 25 redis: 26 image: 'redis:alpine' 27 ports: [6379] 28 environment: 29 TZ: Etc/UTC 30 volumes: 31 - redis-data:/data
Un coup de docker-compose up --detach
pour démarrer les containers Docker, et c'est parti !
Pour stopper les containers, lancer simplement docker-compose stop
.
En lançant symfony var:export --multiline
, une liste de variables d'environnement semblable devrait s'afficher :
1export DATABASE_DATABASE=app 2export DATABASE_NAME=app 3export DATABASE_URL=postgres://app:app@127.0.0.1:49160/app?sslmode=disable&charset=utf8 4export REDIS_URL=redis://127.0.0.1:49234 5# ...
Si c'est le cas, félicitations ! Le binaire symfony
a correctement détecté vos containers Docker et vous pouvez lancer symfony serve
.
En résumé
En résumé, que faut-il pour bénéficier de ce nouvel environnement de développement ?
- Avoir PHP installé localement
- Avoir Node.js installé localement (ou non)
- Utiliser Docker pour les bases de données et Redis
- Utiliser le Symfony CLI en tant que serveur web, proxy pour le nom de domaine, le https, et l'intégration Docker
Mise en oeuvre sur un projet :
- Créer un
docker-compose.yaml
à votre sauce - Puis lancer les commandes suivantes :
1symfony server:ca:install 2symfony proxy:start 3symfony proxy:domain:attach my-app 4docker-compose up --detach 5symfony serve
Enjoy !
Pour aller plus loin
Utiliser Manala et les recipes
Pour faciliter la maintenance, l'évolutivité, la configuration et la gestion de ces toutes étapes à travers nos différents projets, on utilise l'outil Manala
permettant l'utilisation d'un système de recipes (recettes) et de générer automatiquement plusieurs fichiers à partir d'un seul point d'entrée : le fichier de configuration .manala.yaml
.
Il y a des recipes officielles fournies par Manala, mais ça ne nous convenait pas forcément.
On a donc créé notre propre repository de recipes, avec une recipe yprox.app-docker
qui permet :
- de définir la timezone du projet, qui sera injectée dans le
php.ini
et dans les containers Docker - de définir une configuration PHP, qui sera injectée dans le
php.ini
- de définir quelle base de données et quelle version utiliser, qui modifiera le
docker-compose.yaml
- de mettre à disposition quelques commandes :
-
make setup
: à exécuter qu'une seule fois, pour setuper le projet, créer les containers Docker... -
make up
: pour lancer le proxy local Symfony et les containers Docker -
make halt
: pour stopper les containers Docker (quand la journée est finie :stuck_out_tongue: ) -
make destroy
: pour supprimer les containers Docker et volumes associés
-
La recipe s'installe facilement :
1manala init -i yprox.app-docker --repository https://github.com/Yproximite/manala-recipes.git
Pour mettre à jour la recipe, simplement lancer un manala up
🎉.
Il ne me faut pas plus de 2 minutes pour mettre à jour la recipe sur 4 ou 5 projets, c'est un vrai gain de temps considérable !
Bonus : voir Mise en place de l'environnement de développement en ~1 minute pour la mise en place et utilisation
de la recipe Manala yprox.app-docker
.
Mise en place de l'environnement de développement en ~1 minute
Sur un projet existant avec du PHP, Node.js, PostgreSQL et Redis :
- installation de la recipe Manala
yprox.app-docker
- migration du Makefile pour utiliser
make setup
et les$(symfony)
- création des containers Docker
- installation de l'application
Le tout en en ~1 minute :heart_eyes:
Intégration avec le CI ?
Puisqu'on a :
- la version de PHP définie dans le
.php-version
- la version de Node.js définie dans le
.nvmrc
- et un
docker-compose.yaml
pour la base de données et Redis
Est-ce qu'il serait possible d'exploiter tout ça dans le CI ? La réponse est oui.
On utilise GitHub Actions comme CI, et il est plutôt facile de modifier nos workflows pour prendre en compte les éléments listés précédemment.
Installer PHP et Node.js aux bonnes versions
Le soucis étant que les actions shivammathur/setup-php et actions/setup-node ne prennent pas en compte
les .php-version
et .nvmrc
, il va donc falloir trouver une solution.
J'ai pris pour habitude de définir la version de PHP et Node.js à utiliser en tant que variables d'environnement définies au niveau du worklfow :
1# .github/workflows/ci.yml 2name: CI 3 4on: 5 pull_request: 6 types: [opened, synchronize, reopened, ready_for_review] 7 8env: 9 TZ: UTC 10 PHP_VERSION: 7.4 11 NODE_VERSION: 12.x 12 13jobs: 14 php: 15 runs-on: ubuntu-latest 16 steps: 17 - uses: actions/checkout@v2 18 19 - uses: shivammathur/setup-php@v2 20 with: 21 php-version: ${{ env.PHP_VERSION }} 22 coverage: none 23 extensions: iconv, intl 24 ini-values: date.timezone=${{ env.TZ }} 25 tools: symfony 26 27 - uses: actions/setup-node@v2 28 with: 29 node-version: ${{ env.NODE_VERSION }} 30 31 another_job: 32 # ...
Sachant qu'il est possible de définir des variables d'environnement à la volée, pourquoi est-ce qu'on n'utiliserait pas le contenu des fichiers .php-version
et .nvmrc
?
C'est totallement possible en faisant ainsi :
1# .github/workflows/ci.yml 2name: CI 3 4on: 5 pull_request: 6 types: [opened, synchronize, reopened, ready_for_review] 7 8env: 9 TZ: UTC 10- PHP_VERSION: 7.4 11- NODE_VERSION: 12.x 12 13jobs: 14 php: 15 runs-on: ubuntu-latest 16 steps: 17 - uses: actions/checkout@v2 18 19 + - run: echo "PHP_VERSION=$(cat .php-version | xargs)" >> $GITHUB_ENV 20 + - run: echo "NODE_VERSION=$(cat .nvmrc | xargs)" >> $GITHUB_ENV 21 22 # Installation de PHP et Node.js
Lancer Docker
Une commande make setup@integration
est disponible pour lancer Docker sur le CI.
1name: CI 2 3on: 4 pull_request: 5 types: [opened, synchronize, reopened, ready_for_review] 6 7env: 8 TZ: UTC 9 10jobs: 11 php: 12 runs-on: ubuntu-latest 13 steps: 14 - uses: actions/checkout@v2 15 16 - run: echo "PHP_VERSION=$(cat .php-version | xargs)" >> $GITHUB_ENV 17 - run: echo "NODE_VERSION=$(cat .nvmrc | xargs)" >> $GITHUB_ENV 18 19 # Installation de PHP et Node.js 20 21 + # Configure le Symfony CLI et lance les containers Docker 22 + - run: make setup@integration
Exemple complet
On a créé une GitHub Action locale qui définit plusieurs variables d'environnement. De cette façon on peut l'utiliser dans plusieurs jobs et éviter de dupliquer pas mal de lignes :
1# .github/actions/setup-environment/action.yml 2name: Setup environment 3description: Setup environment 4runs: 5 using: 'composite' 6 steps: 7 - run: echo "PHP_VERSION=$(cat .php-version | xargs)" >> $GITHUB_ENV 8 shell: bash 9 10 - run: echo "NODE_VERSION=$(cat .nvmrc | xargs)" >> $GITHUB_ENV 11 shell: bash 12 13 # Composer cache 14 - id: composer-cache 15 run: echo "::set-output name=dir::$(composer global config cache-files-dir)" 16 shell: bash 17 18 - run: echo "COMPOSER_CACHE_DIR=${{ steps.composer-cache.outputs.dir }}" >> $GITHUB_ENV 19 shell: bash 20 21 # Yarn cache 22 - id: yarn-cache-dir 23 run: echo "::set-output name=dir::$(yarn cache dir)" 24 shell: bash 25 26 - run: echo "YARN_CACHE_DIR=${{ steps.yarn-cache-dir.outputs.dir }}" >> $GITHUB_ENV 27 shell: bash 28 29 # Misc 30 - run: echo "IS_DEPENDABOT=${{ startsWith(github.head_ref, 'dependabot') == true }}" >> $GITHUB_ENV 31 shell: bash
Et un exemple de workflow pour PHP, nos assets JavaScript, et des tests E2E Cypress + auto-approve si c'est une PR Dependabot :
1name: CI 2 3on: 4 pull_request: 5 types: [opened, synchronize, reopened, ready_for_review] 6 7env: 8 TZ: UTC 9 10 COMPOSER_ALLOW_SUPERUSER: '1' # https://getcomposer.org/doc/03-cli.md#composer-allow-superuser 11 # À décommenter si vous utilisez Packagist.com 12 #COMPOSER_AUTH: '{"http-basic":{"repo.packagist.com":{"username":"token","password":"${{ secrets.PACKAGIST_AUTH_TOKEN }}"}}}' 13 14jobs: 15 php: 16 runs-on: ubuntu-latest 17 steps: 18 - uses: actions/checkout@v2 19 20 - uses: ./.github/actions/setup-environment 21 22 - uses: shivammathur/setup-php@v2 23 with: 24 php-version: ${{ env.PHP_VERSION }} 25 coverage: none 26 extensions: iconv, intl 27 ini-values: date.timezone=${{ env.TZ }} 28 tools: symfony 29 30 - uses: actions/setup-node@v2 31 with: 32 node-version: ${{ env.NODE_VERSION }} 33 34 - uses: actions/cache@v2 35 with: 36 path: ${{ env.COMPOSER_CACHE_DIR }} 37 key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 38 restore-keys: ${{ runner.os }}-composer- 39 40 - uses: actions/cache@v2 41 with: 42 path: ${{ env.YARN_CACHE_DIR }} 43 key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 44 restore-keys: ${{ runner.os }}-yarn- 45 46 # Check des dépendances 47 - run: symfony composer validate 48 - run: symfony security:check 49 50 # Installe l'environnement et l'application 51 - run: make setup@integration 52 53 # Préparation des tests 54 - run: symfony console cache:clear 55 - run: APP_ENV=test symfony console doctrine:schema:validate # force APP_ENV=test because only the test database is created 56 - run: symfony console api:swagger:export > /dev/null # Check if ApiPlatform is correctly configured 57 58 # Lint des fichiers Twig, Yaml et XLIFF 59 - run: symfony console lint:twig templates 60 - run: symfony console lint:yaml config --parse-tags 61 - run: symfony console lint:xliff translations 62 63 # Outils d'analyse de code statique 64 - run: symfony php bin/php-cs-fixer.phar fix --verbose --diff --dry-run 65 - run: symfony php bin/phpcs 66 - run: symfony php bin/phpstan analyse 67 - run: APP_ENV=test symfony php bin/phpunit.phar # See https://github.com/symfony/symfony-docs/pull/15228 68 - run: symfony php bin/phpspec run 69 70 javascript: 71 runs-on: ubuntu-latest 72 steps: 73 - uses: actions/checkout@v2 74 75 - uses: ./.github/actions/setup-environment 76 77 - uses: shivammathur/setup-php@v2 78 with: 79 php-version: ${{ env.PHP_VERSION }} 80 coverage: none 81 extensions: iconv, intl 82 ini-values: date.timezone=${{ env.TZ }} 83 tools: symfony 84 85 - uses: actions/setup-node@v2 86 with: 87 node-version: ${{ env.NODE_VERSION }} 88 89 - uses: actions/cache@v2 90 with: 91 path: ${{ env.COMPOSER_CACHE_DIR }} 92 key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 93 restore-keys: ${{ runner.os }}-composer- 94 95 - uses: actions/cache@v2 96 with: 97 path: ${{ env.YARN_CACHE_DIR }} 98 key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 99 restore-keys: ${{ runner.os }}-yarn- 100 101 - run: make setup@integration 102 103 # Check des types TypeScript 104 - run: yarn tsc --noEmit 105 106 # Lint des fichiers JS et CSS 107 - run: yarn lint:js --no-fix 108 - run: yarn lint:css --no-fix 109 110 # Build pour le développement et production 111 - run: yarn dev 112 - run: yarn prod 113 114 cypress: 115 runs-on: ubuntu-latest 116 name: cypress (${{ matrix.cypress.group }}) 117 strategy: 118 fail-fast: false 119 matrix: 120 cypress: 121 # Ajouter plus d'entrées pour bénificier de la parallélisation 122 - group: default 123 spec: 'tests/cypress/**/*' 124 125 steps: 126 - uses: actions/checkout@v2 127 128 - uses: ./.github/actions/setup-environment 129 130 - uses: shivammathur/setup-php@v2 131 with: 132 php-version: ${{ env.PHP_VERSION }} 133 coverage: none 134 extensions: iconv, intl 135 ini-values: date.timezone=${{ env.TZ }} 136 tools: symfony 137 138 - uses: actions/setup-node@v2 139 with: 140 node-version: ${{ env.NODE_VERSION }} 141 142 - uses: actions/cache@v2 143 with: 144 path: ${{ env.COMPOSER_CACHE_DIR }} 145 key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 146 restore-keys: ${{ runner.os }}-composer- 147 148 - uses: actions/cache@v2 149 with: 150 path: ${{ env.YARN_CACHE_DIR }} 151 key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 152 restore-keys: ${{ runner.os }}-yarn- 153 154 - run: make setup@integration 155 156 # Démarrage du serveur Symfony 157 - run: APP_ENV=test symfony serve --port 8000 --daemon 158 - run: echo "CYPRESS_BASE_URL=https://localhost:8000" >> $GITHUB_ENV 159 160 - name: Run Cypress 161 if: ${{ env.IS_DEPENDABOT == 'false' && ! github.event.pull_request.draft }} 162 uses: cypress-io/github-action@v2 163 with: 164 spec: ${{ matrix.cypress.spec }} 165 # À décommenter si le projet est configuré sur le Cypress Dashboard 166 #record: true 167 #parallel: true 168 #group: ${{ matrix.cypress.group }} 169 #env: 170 # CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} 171 172 - name: Run Cypress (for Dependabot or when pull request is draft) 173 if: ${{ env.IS_DEPENDABOT == 'true' || github.event.pull_request.draft }} 174 uses: cypress-io/github-action@v2 175 176 auto_approve: 177 runs-on: ubuntu-latest 178 needs: [php, javascript, cypress] 179 if: ${{ github.actor == 'dependabot[bot]' }} 180 steps: 181 - uses: hmarr/auto-approve-action@v2.0.0 182 with: 183 github-token: ${{ secrets.GITHUB_TOKEN }}