TL;DR
Après la mise à jour vers N8N 2.0, vos workflows utilisant $env.MA_VARIABLE échouent avec l'erreur "access to env vars denied". La cause : le mode External Runners exécute le code dans un container séparé qui n'a pas accès aux variables d'environnement du container N8N principal.
Solutions :
- Réseau Docker dédié + URL directe par nom de container (recommandé)
- Configuration du launcher pour autoriser les variables
- N8N Variables (fonctionnalité payante)
Note sécurité : Ne connectez PAS vos services au réseau interne N8N (infrastructure). Créez un réseau dédié pour la communication.
# ❌ Ne fonctionne plus avec external runners {{ $env.API_URL }}/endpoint # ✅ Solution : URL directe via nom de container Docker http://mon-service:5001/endpoint
Le Problème : Variables d'Environnement Inaccessibles
Symptômes
Après la mise à jour vers N8N 2.0, vos workflows qui utilisaient des variables d'environnement via $env cessent de fonctionner :
Error: access to env vars denied
Ou encore :
The connection cannot be established, this usually occurs due to an incorrect host (domain) value
Exemple de code qui ne fonctionne plus
// ❌ Expression N8N qui échoue en mode external runners {{ $env.API_CALCULATEUR_URL ?? 'http://localhost:5001' }}/calculer
Pourquoi Ce Changement : External Runners
Architecture N8N 2.0
N8N 2.0 introduit les Task Runners externes pour améliorer la sécurité et l'isolation du code.
flowchart LR
subgraph docker["Docker Host"]
subgraph n8n["Container N8N"]
n8n_ui["Interface UI"]
n8n_api["API"]
n8n_env["✅ $env vars"]
end
subgraph runners["Container Runners"]
run_code["Exécute Code"]
run_py["Python / JS"]
run_env["❌ $env vars"]
end
end
n8n <--> runners
Configuration typique docker-compose
services: n8n: image: n8nio/n8n:latest environment: N8N_RUNNERS_ENABLED: true N8N_RUNNERS_MODE: external # ← Le code s'exécute ailleurs API_URL: http://localhost:5001 # ← Variable définie ici task-runners: image: n8nio/runners environment: # API_URL n'est PAS définie ici → $env.API_URL = undefined
Le problème expliqué
| Élément | Container N8N | Container Runners |
|---|---|---|
$env.API_URL |
✅ Accessible | ❌ Non accessible |
| Code Python/JS | ❌ Non exécuté ici | ✅ Exécuté ici |
| Interface UI | ✅ | ❌ |
Résultat : L'expression $env.API_URL est évaluée dans le container runners qui ne possède pas cette variable.
Solution 1 : Réseau Docker Dédié (Recommandée)
Considérations de Sécurité
Avant de connecter vos services au réseau N8N, comprenez les implications de sécurité :
| Approche | Risque | Isolation |
|---|---|---|
| Naïve : Tout sur le même réseau | Élevé | Votre service peut accéder à PostgreSQL, Redis, etc. |
| Best Practice : Réseau dédié | Faible | Seule la communication N8N ↔ Service est possible |
Principe de moindre privilège : Un container ne devrait voir que les réseaux strictement nécessaires à son fonctionnement. (OWASP Container Security)
Le problème réseau initial
Si vos services tournent sur des réseaux Docker différents, ils ne peuvent pas communiquer par nom de container :
flowchart LR
subgraph infra["reseau-infra"]
n8n["n8n"]
runners["runners"]
end
subgraph app["reseau-app"]
service["mon-service :5001"]
end
n8n -.->|"❌ ÉCHEC"| service
Problèmes :
host.docker.internalne fonctionne pas de manière fiable sur Linux/WSL- Pas de résolution DNS entre réseaux Docker séparés
Approche Naïve (Non Recommandée)
❌ ÉVITEZ CECI : Connecter directement au réseau infrastructure
# ⚠️ DANGEREUX
docker network connect my-infrastructure_default mon-service
Problème : Votre service peut maintenant accéder à PostgreSQL, Redis, et tous les composants internes.
La solution : Réseau dédié pour la communication
Créez un réseau séparé uniquement pour la communication N8N ↔ Services externes :
# 1. Créer le réseau dédié docker network create n8n-services-net # 2. Connecter N8N à ce nouveau réseau docker network connect n8n-services-net n8n # 3. Connecter votre service à ce réseau (PAS au réseau infrastructure) docker network connect n8n-services-net mon-service
Résultat : Architecture sécurisée avec isolation
flowchart TB
subgraph infra["infrastructure_default (interne)"]
n8n1["n8n"]
pg[("PostgreSQL")]
end
subgraph services["n8n-services-net (dédié)"]
n8n2["n8n"]
svc["mon-service :5001"]
end
n8n1 --- n8n2
n8n2 <-->|"✅ OK"| svc
svc x--x|"❌ Bloqué"| pg
Isolation : mon-service ne peut PAS accéder à PostgreSQL ✓
Étapes de mise en œuvre
Étape 1 : Créer le réseau dédié
# Créer un réseau spécifique pour la communication N8N ↔ Services
docker network create n8n-services-net
Étape 2 : Connecter N8N au nouveau réseau
# N8N doit être sur les 2 réseaux : infrastructure (interne) + services (externe)
docker network connect n8n-services-net n8n
Étape 3 : Connecter votre service au réseau dédié
# ✅ Correct : réseau dédié docker network connect n8n-services-net mon-service # ❌ Éviter : réseau infrastructure # docker network connect my-infrastructure_default mon-service
Étape 4 : Vérifier l'isolation
# Vérifier que mon-service n'a PAS accès au réseau infrastructure docker inspect mon-service --format='{{range $k, $v := .NetworkSettings.Networks}}{{$k}} {{end}}' # Attendu : n8n-services-net (PAS my-infrastructure_default) # Vérifier que N8N est sur les 2 réseaux docker inspect n8n --format='{{range $k, $v := .NetworkSettings.Networks}}{{$k}} {{end}}' # Attendu : my-infrastructure_default n8n-services-net # Tester la connectivité depuis N8N docker exec -it n8n curl http://mon-service:5001/health
Étape 5 : Modifier le workflow N8N
Remplacez l'expression $env par l'URL directe :
| Avant | Après |
|---|---|
{{ $env.API_URL }}/endpoint |
http://mon-service:5001/endpoint |
Solution 2 : Configurer le Launcher (Avancée)
Si vous devez absolument utiliser des variables d'environnement dans vos Code nodes, configurez le launcher des runners.
Étape 1 : Créer le fichier de configuration
Créez n8n-task-runners.json :
{
"task-runners": [
{
"runner-type": "javascript",
"allowed-env": [
"API_URL",
"DATABASE_URL",
"SECRET_KEY"
]
},
{
"runner-type": "python",
"allowed-env": [
"API_URL",
"DATABASE_URL"
]
}
]
}
Étape 2 : Monter le fichier dans le container
services: task-runners: image: n8nio/runners environment: - N8N_RUNNERS_TASK_BROKER_URI=http://n8n:5679 - N8N_RUNNERS_AUTH_TOKEN=your-secret-here - API_URL=http://mon-service:5001 # ← Définir ici aussi volumes: - ./n8n-task-runners.json:/etc/n8n-task-runners.json:ro
Note : Cette solution nécessite de définir les variables dans les deux containers ET de les autoriser dans le launcher.
Solution 3 : N8N Variables (Payante)
N8N propose une fonctionnalité "Variables" qui résout ce problème, mais elle nécessite une licence payante.
Accès
Settings → Variables → "Upgrade to unlock variables"
Utilisation (si disponible)
// Dans les expressions {{ $vars.API_URL }} // Dans le code Python _vars.API_URL
Tableau Comparatif des Solutions
| Solution | Complexité | Coût | Sécurité | Recommandation |
|---|---|---|---|---|
| Réseau Docker dédié | Moyenne | Gratuit | Haute | ✅ Recommandée |
| Réseau partagé (naïf) | Faible | Gratuit | Faible | ❌ Non recommandé |
| Config Launcher | Élevée | Gratuit | Moyenne | Pour cas spécifiques |
| N8N Variables | Faible | Payant | Haute | Si licence disponible |
Troubleshooting
Cause : Utilisation de $env dans un workflow exécuté par external runners.
Solution : Remplacez {{ $env.API_URL }}/endpoint par une URL directe http://mon-service:5001/endpoint
Causes possibles :
- Containers sur des réseaux Docker différents
host.docker.internalsur Linux/WSL- Service non démarré
Diagnostic :
# Vérifier les réseaux du container docker inspect mon-service --format='{{range $k, $v := .NetworkSettings.Networks}}{{$k}} {{end}}' # Tester la connectivité depuis n8n docker exec -it n8n ping mon-service docker exec -it n8n curl http://mon-service:5001/health
Cause : host.docker.internal ne fonctionne pas sur Linux/WSL.
Solution : Utiliser le nom du container Docker au lieu de host.docker.internal.
Cause : Les tests manuels dans l'UI N8N peuvent utiliser un chemin d'exécution différent.
Vérification :
- Assurez-vous que
N8N_RUNNERS_MODE: externalest bien configuré - Testez avec un webhook réel, pas juste le bouton "Execute"
Bonnes Pratiques
1. Éviter $env avec External Runners
// ❌ Éviter {{ $env.SERVICE_URL }}/api // ✅ Préférer http://service-name:port/api
2. Documenter les URLs hardcodées
// URL du service calculateur // Container: calculateur-app sur réseau dédié n8n-services-net // Note: NE PAS connecter au réseau infrastructure pour isolation sécurité http://calculateur-app:5001/calculer
3. Script de déploiement automatisé (sécurisé)
#!/bin/bash # deploy.sh - Utilise un réseau dédié pour l'isolation # Créer le réseau dédié si nécessaire docker network create n8n-services-net 2>/dev/null || true # Démarrer le container docker-compose up -d # Attendre que le container soit prêt sleep 3 # Connecter N8N au réseau dédié (si pas déjà fait) docker network connect n8n-services-net n8n 2>/dev/null || true # Connecter le service au réseau dédié (PAS au réseau infrastructure) docker network connect n8n-services-net mon-service 2>/dev/null || echo "Déjà connecté" echo "Déploiement terminé avec isolation sécurisée"
4. Vérifier après chaque redémarrage
La connexion réseau est perdue après un docker-compose down. Ajoutez la reconnexion dans vos scripts.
FAQ
Les External Runners améliorent la sécurité et l'isolation du code exécuté dans les Code nodes. Le code utilisateur s'exécute dans un container séparé avec des permissions limitées, protégeant le container N8N principal.
Techniquement oui, en utilisant N8N_RUNNERS_MODE=internal, mais ce n'est pas recommandé pour des raisons de sécurité. Les futures versions de N8N pourraient supprimer cette option.
Oui, les expressions standard de N8N fonctionnent. Seul l'accès aux variables d'environnement via $env est restreint dans les runners.
Oui, si vous utilisez docker-compose down. La connexion réseau externe n'est pas persistante. Incluez la commande docker network connect dans vos scripts de déploiement.
C'est dangereux. En connectant votre service au réseau my-infrastructure_default, il peut accéder à :
- PostgreSQL (port 5432) : lecture/écriture des données
- Redis (si présent) : sessions, cache
- Task Runners : potentielle escalade de privilèges
Solution sécurisée : Créez un réseau dédié n8n-services-net où seuls N8N et vos services peuvent communiquer, sans exposer les composants internes.
Checklist de Migration
- Identifier tous les workflows utilisant
$env - Créer le réseau dédié
n8n-services-net - Connecter N8N au réseau dédié
- Pour chaque service externe : connecter au réseau dédié (pas infrastructure)
- Vérifier l'isolation : le service ne doit PAS voir PostgreSQL
- Remplacer
$env.URLpar les noms de containers - Tester chaque workflow modifié
- Mettre à jour les scripts de déploiement (réseau dédié)
- Documenter les URLs dans les workflows
Ressources
-
N8N Task Runners Documentation
Configuration officielle des runners
-
N8N Environment Variables
Liste complète des variables
-
Docker Networking
Guide Docker officiel
-
OWASP Docker Security Cheat Sheet
Best practices sécurité containers
Conclusion
La migration vers N8N 2.0 avec External Runners introduit des restrictions sur l'accès aux variables d'environnement pour des raisons de sécurité. La solution la plus robuste est d'utiliser un réseau Docker dédié (pas le réseau infrastructure interne) et des URLs directes par nom de container.
Points clés à retenir :
$envn'est pas accessible dans les External Runners par défaut- Créez un réseau dédié pour la communication N8N ↔ Services externes
- Ne connectez jamais vos services au réseau infrastructure interne
- Utilisez les noms de containers comme hostnames
- Mettez à jour vos scripts de déploiement
Sécurité : Le principe de moindre privilège s'applique aussi aux réseaux Docker. Un service externe ne devrait jamais pouvoir accéder à votre base de données.