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
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.)