javascriptnode.jsdockerdocker-composeenvironment-variables

Error Handling Environment Variables Natively in Node.jsv22 Within a Container


I am trying to work with .env files natively, as starting from Node.jsv20 (I am currently using version v22.14.0), it is no longer necessary to install libraries like dotenv to manage environment variables.

However, when building my container, I encounter an error. Here is how my project is structured:

project/
├── .env
├── docker-compose.yml
└── backend/
    └── server/
        ├── server.js
        └── config.js
error:
node:internal/process/per_thread:262                                                                                                                  
server-1   |       _loadEnvFile();
server-1   |       ^                                                                                                                                             
server-1   |                                                                                                                                                     
server-1   | Error: ENOENT: no such file or directory, open '.env'                                                                                               
server-1   |     at loadEnvFile (node:internal/process/per_thread:262:7)
server-1   |     at file:///app/server/server.js:8:1                                                                                                             
server-1   |     at ModuleJob.run (node:internal/modules/esm/module_job:234:25)                                                                                  
server-1   |     at async ModuleLoader.import (node:internal/modules/esm/loader:473:24)
server-1   |     at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:122:5) {                                                               
server-1   |   errno: -2,                                                                                                                                        
server-1   |   code: 'ENOENT',                                                                                                                                   
server-1   |   syscall: 'open',
server-1   |   path: '.env'                                                                                                                                      
server-1   | }                                                                                                                                                   

.env

NODE_LOCAL_PORT=4000

Server

import express from 'express';
import morgan from 'morgan';
import cors from 'cors';
process.loadEnvFile('../../.env')

const app = express();
app.use(cors());
app.use(morgan('dev'));

app.get('/', (req, res) => {
    res.json({ frutas: ['manzana', 'pera', 'limón', 'mango', 'kiwi', 'melón', 'papaya'] });
});

const PORT = process.env.NODE_LOCAL_PORT || 4000;

app.listen(PORT, () => {
    console.log(`Server started on port ${PORT} 🚀`);
});

Docker-file:

# Usa una imagen base de Node.js
FROM node:lts

# Establece el directorio de trabajo en el contenedor
WORKDIR /app

ENV NODE_ENV=production

# Copia package.json y package-lock.json para instalar dependencias primero
COPY package*.json ./

# Instala las dependencias
RUN npm install
RUN npm install -g nodemon

# Copia el resto de los archivos de la aplicación
COPY . . 

# Expone el puerto en el que correrá el servidor
EXPOSE 4000

# Comando para iniciar el servidor
ENTRYPOINT ["nodemon"]
CMD ["server/server.js"]

Docker-compose:

services:
  server:
    build:
      context: backend
      dockerfile: Dockerfile
    env_file:
      - ./.env
    develop:
      watch:
        - action: sync
          path: backend/ 
          target: /app
          ignore:
            - node_modules/
        - action: rebuild
          path: ./backend/package.json

    ports:
      - 4000:4000

Solution

  • You are loading the .env file with

    process.loadEnvFile('../../.env')
    

    The file path ../../.env is resolved relative to the Node.js process's current working directory. It is not resolved relative to the location of the server.js script.

    In your Docker container, your are setting the working directory with WORKDIR /app and you are copying the files from your project directory into the /app directory with COPY . .

    Your .env file ends up directly in the container's working directory; not two directories up. (Assuming you don't have a .dockerignore file that ignores .env.)

    So, the correct path for loading the .env file would be just .env:

    process.loadEnvFile('.env');
    

    This is the default argument for loadEnvFile, so you might as well just omit it:

    process.loadEnvFile();
    

    Or, alternatively, if you want to locate the .env file at a stable path that does not depend on the process's working directory, you join the relative path from server.js to .env with the absolute path to the server directory:

    import { join } from 'node:path';
    
    process.loadEnvFile(join(import.meta.dirname, '../../.env'));
    

    Or, just don't call process.loadEnvFile at all. It seems redundant in your current setup. When you are running the server via Docker Compose and loading the .env file with the env_file option, the environment variables are already available within the containerized Node.js process. (This is arguably the best approach. You may even want to go a step further and explicitly exclude the .env file from your container image; especially so if you are planning to include secrets, like passwords or API keys.)