n8n Deployment: When Environment Variables Don't Work As Expected

Deploying n8n to Production: When Environment Variables Betray You
The ai-agents-admin-agent project had eight n8n workflows ready to ship to a Linux server. Everything looked good until the first deployment logs scrolled in: no such table: users. Every single workflow failed. The problem? All the SQLite nodes were pointing to C:\projects\ai-agents\admin-agent\database\admin_agent.db—a Windows path that didn’t exist on the server.
The obvious fix seemed elegant: use n8n’s expression system. Store the database path as $env.DATABASE_PATH, reference it in each node, and let the runtime handle it. The team added the variable to docker-compose.yml for local development and deployed with confidence. But when they tested the API calls, the workflows still tried to access that Windows path. After digging through n8n v2.4.5’s task runner behavior, it became clear that environment variables weren’t being passed to the SQLite node execution context the way the team expected. The expression was stored, but the actual runtime didn’t resolve it.
This was the moment to abandon elegant solutions in favor of something that actually works. The team implemented deploy-time path replacement. Instead of trusting runtime resolution, a custom deployment script in deploy.config.js intercepts the workflow JSON before uploading it to the server. It finds every instance of $env.DATABASE_PATH and replaces it with /var/lib/n8n/data/admin_agent.db—the actual path where the database would live in production. Simple string manipulation, guaranteed to work.
But there was another problem: n8n stores workflows in two states—stored (in the database) and active (loaded in memory). Updating a workflow through the API only touches the stored version. The active workflow keeps running with its old parameters. The deployment process had to explicitly deactivate and reactivate each workflow to force n8n to reload the configuration into memory.
The final deployment pipeline grew to include SSH-based file transfer, database schema initialization (copying schema.sql and seed_questions.sql to the server and executing them), and a migration system for incremental database updates. Now, running node deploy/deploy-n8n.js --env .env.deploy handles all of it: path replacement, database setup, and workflow activation.
The real lesson? Don’t rely on relative paths or runtime expressions for critical parameters in containerized workflows. The process working directory inside Docker is unpredictable—it could be anywhere depending on how the container started. Environment variable resolution depends on how your application reads them, and not every library respects them equally. Sometimes the straightforward approach—knowing exactly where your application will run and substituting the correct path at deployment time—is more reliable than hoping elegant abstraction layers will work as expected.
😄 Why is Linux safe? Hackers peer through Windows only.
Metadata
- Session ID:
- grouped_C--projects-bot-social-publisher_20260207_1900
- Branch:
- main
- Dev Joke
- GitHub — единственная технология, где «это работает» считается документацией.