Zum Hauptinhalt springen
Echtzeit-Radar & Feeds
Alle RSS Feeds ➔
👥 Community & Social
Windows Tipps & SecurityGrafikkarte vor Überhitzung schützen: So geht’s(25.09.2026 um 08:00 Uhr)
••••••••••
Windows Tipps & SecurityGrafikkarte vor Überhitzung schützen: So geht’s(25.09.2026 um 08:00 Uhr)
••••••••••
Intelligence View
⚡ tsecurity.de Intelligence

¿Cómo ejecutar Node.js, PostgreSQL y Express.js en contenedores dentro de un entorno de desarrollo?

Si no quieres depender de algún entorno de desarrollo integrado (IDE) en la nube (GitHub Codespaces, Firebase Studio, Replit, etc) la clave es usar Docker, Docker Compose, Git, y un repositorio en la nube como GitHub, GitLab o Bitbucket. …

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!

Si no quieres depender de algún entorno de desarrollo integrado (IDE) en la nube (GitHub Codespaces, Firebase Studio, Replit, etc) la clave es usar Docker, Docker Compose, Git, y un repositorio en la nube como GitHub, GitLab o Bitbucket.






Descripción general:



Este tutorial consiste en Dockerizar una aplicación Backend de Node.js y una base de datos de PostgresSQL, controlando todo el stack de la aplicación con un solo archivo. Los temas cubiertos incluye:




  • Ejecutar PostgresSQL, Adminer en un contenedor local.

  • Conterizar aplicación Node.js.

  • Configurar archivo Docker Compose y Dockerfile.






Definiciones.




  • Docker

    Dado que Docker está disponible y es compatible con Linux, macOS y Windows, la mayoría del software puede ser utilizado en cualquier computadora. Docker simplifica y flexibiliza el proceso de configuración de la aplicación, permitiéndole implementar, administrar y escalar bases de datos en contenedores aislados con solo unos pocos comandos.


  • Docker Compose

    Docker Compose simplifica el control de toda tu stack de aplicaciones, lo que facilita la gestión de servicios, redes y volúmenes en un único archivo de configuración YAML comprensible. Luego, con un solo comando, puedes crear y iniciar todos los servicios desde tu archivo de configuración.


  • Git

    Es un sistema de control de versiones que permite a los desarrolladores guardar diferentes versiones de archivos y rastrear los cambios realizados en un proyecto.


  • GitHub

    Es una plataforma basada en la nube que aloja repositorios de código y facilita la colaboración entre desarrolladores, permitiendo que trabajen en proyectos simultáneamente y mantengan un seguimiento de los cambios.







Requisitos:




  • Instalar Docker.

  • Instalar Docker Compose.

  • Instalar Node.js.

  • Instalar Git.

  • Una cuenta de GitHub.



Si clonas el repositorio de GitHub, ve al Paso 2 - Dockeriza la aplicación..






Paso 1 - Crea el proyecto local.



A continuación, se va a describir cómo crear la app con Node.js sin Docker, con una breve explicación de cada una de sus partes.






1. Crea el directorio del proyecto:



Pega los siguientes comandos en tu terminal:




mkdir members-only
cd members-only










2. Crea los archivos y directorios de la aplicación:



Pega los siguientes comandos en tu terminal:




mkdir src controllers db middleware public routes views
touch package.json









3. Configurar tu package.json:



En el package.json pega lo siguiente:




{
"name": "docker-compose",
"version": "1.0.0",
"private": true,
"main": "src/app.js",
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"scripts": {
"start": "nodemon src/app.js",
"deploy": "node src/app.js",
"formatter": "semistandard --fix"
},
"dependencies": {
"bcrypt": "5.1.1",
"dotenv": "16.4.7",
"ejs": "3.1.10",
"express": "4.21.2",
"express-session": "1.18.1",
"express-validator": "7.2.1",
"passport": "0.7.0",
"passport-local": "1.0.0",
"pg": "8.13.3"
},
"devDependencies": {
"nodemon": "3.1.9",
"semistandard": "17.0.0"
},
"eslintConfig": {
"extends": [
"./node_modules/semistandard/eslintrc.json"
]
},
"nodemonConfig": {
"watch": [
"src"
],
"ignore": [
"*.test.ts"
],
"delay": "3",
"execMap": {
"ts": "ts-node"
}
}
}






IMPORTANTE: Debido a que la aplicación se ejecutará dentro de un contenedor, Te recomiendo utilizar nodemon en lugar de node --watch. Este último presenta inestabilidad en contenedores, ya que solo detecta modificaciones en los archivos una única vez y deja de monitorearlos posteriormente.



Explicando package.json:



"main": "src/app.js", El archivo principal es src/app.js.

“scripts”:

start: Corre la app con nodemon (modo desarrollo).

deploy: Corre la app en producción

formatter: Formatea el código con semistandard.



“Dependencies”:



express: Servidor web.

ejs: Plantillas HTML.

dotenv: Variables de entorno.

bcrypt: Encriptación.

passport / passport-local: Autenticación.

express-session: Manejo de sesiones.

express-validator: Validación de datos.

pg: Conexión a PostgreSQL.



“devDependencies”:

nodemon: Recarga automática en desarrollo.

semistandard: Reglas de estilo.



nodemon observa src/, ignora .test.ts, espera 3 segundos para reiniciar.





4. Configura app.js.



Crea el archivo app.js en el directorio src:




touch src/app.js






Pega el siguiente código en src/app.js:




const path = require("node:path");
const express = require("express");

const session = require("express-session");
const passport = require("passport");

const routes = require("./routes/index.js")

const app = express();

app.set("views", path.join(__dirname, "views"));
app.set("view engine", "ejs");

app.use(express.static(path.join(__dirname, "public")));

app.use(session({ secret: "cats", resave: false, saveUninitialized: false }));
app.use(passport.session());
app.use(express.urlencoded({ extended: false }));

// routes
app.use(routes);

const PORT = process.env.PORT_NODE_SERVER ?? 3000;
const IP_NODE_SERVER = process.env.IP_NODE_SERVER ?? "localhost";

app.listen(PORT, () => {
console.log(`http://${IP_NODE_SERVER}:${PORT}`);
});






Importa módulos:

path: Manejo de rutas de archivos.

express: Framework del servidor.

express-session: Manejo de sesiones.

passport: Autenticación.

routes: Rutas de la aplicación.



Inicializa Express:

Crea una instancia de la app (app).



Configuración del motor de vistas:

Las vistas están en ./views.



Usa EJS para renderizar HTML dinámico.



Archivos estáticos:

Sirve archivos desde la carpeta ./public.



Sesiones y autenticación:

Usa express-session con una clave secreta.

Configura passport para manejar la sesión.

Acepta formularios codificados (urlencoded).



Define rutas:

Carga las rutas desde ./routes/index.js.



Servidor:

Usa el puerto y la IP de las variables de entorno o por defecto localhost:3000.



Aun el proyecto no funcionara, falta crear la base de datos, las consultas, las rutas y las vistas





5. Routes



Pega el siguiente código en routes/index.js:




const express = require("express");

const indexController = require("../controllers/indexController.js");
const loginInController = require("../controllers/logInController.js");
const logOutController = require("../controllers/logOutController.js");
const memberJoinController = require("../controllers/memberJoinController.js");
const signUpController = require("../controllers/signUpController.js");
const createMsgController = require("../controllers/createMsgController.js");

const middlewareSignUpValidation = require("../middleware/validation/signUpValidation.js");
const middlewareCreateMsgValidation = require("../middleware/validation/createMsgValidation.js");

const router = express.Router();

router.get("/", indexController.getIndex);
router.get("/log-in", loginInController.getLoginForm);
router.get("/sign-up", signUpController.getSignupForm);
router.get("/log-out", logOutController.getLogout);
router.get("/member-join", memberJoinController.getMemberJoinForm);
router.get("/create-msg", createMsgController.getCreateMsgForm );


router.post(
"/sign-up",
middlewareSignUpValidation.validateUser(),
signUpController.create
);

router.post(
"/log-in",
loginInController.passportLocalStrategy.authenticate("local", {
successRedirect: "/",
failureRedirect: "/log-in",
})
);

router.post("/member-join", memberJoinController.confirmSecretAccess);

router.post("/create-msg", middlewareCreateMsgValidation.validateMsg(), createMsgController.create);

router.all("*", (req, res) => {
if (req.isAuthenticated()) {
res.status(404).render("404");
} else {
res.redirect("/log-in");
}
});

module.exports = router;







Aquí es donde se organiza las rutas de la aplicación y asigna un controlador. Cuando el usuario accede a una ruta (por ejemplo, inicio, ingreso, registro o creación de mensajes), se invoca el controlador correspondiente para generar el formulario o la vista.






6. BD



En src/bd/pool.js pega el siguiente codigo:




const { Pool } = require("pg");

require("dotenv").config();

const pool = new Pool({
host: process.env.HOSTS_DB, // or wherever the db is hosted
port: process.env.PORT_DB,
user: process.env.USER_DB,
password: process.env.PASSWORD_DB,
database: process.env.DATABASE_DB, // The default port
});

module.exports = pool;






En src/db/query.js pega el siguiente código:




const pool = require("./pool.js");

async function getAllTable(table) {
const { rows } = await pool.query(`SELECT * FROM ${table};`);
return rows;
}

async function getAllmsg() {
const { rows } = await pool.query(
`SELECT u.username, m.date, m.description, m.title FROM logs_messages m INNER JOIN users u ON u.id = m.user_id;`
);
return rows;
}

async function getSecretCode(username) {
const { rows } = await pool.query(
`SELECT username FROM users WHERE username = '${username}';`
);
// console.log(rows);

return rows.map((x) => x.username).toString();
}

async function updateStatusMember(id, secretCode) {
const { rows } = await pool.query(
`UPDATE users
SET is_member = true
WHERE id = '
${id}' AND username = '${secretCode}';`
);

return rows;
}

async function insertMsg (id, title, description) {
const { rows } = await pool.query(
"insert into logs_messages (user_id, title, description) values ($1, $2, $3)",
[id, title, description]
);

return rows;
}

module.exports = {
getAllTable,
getAllmsg,
getSecretCode,
updateStatusMember,
insertMsg,
};







  • pool.js crea y exporta un pool de conexiones a PostgreSQL.

  • query.js – ejecuta consultas SQL.
    ### 7. Controlladores




touch createMsgController.js logInController.js memberJoinController.js indexController.js logOutController.js signUpController.js






En src/controllers/createMsgController.js pega el siguiente codigo:




const db = require("../db/query");
const bcrypt = require("bcrypt");

async function getCreateMsgForm(req, res) {
if (req.isUnauthenticated()) {
res.redirect("/");
} else {
res.render("pages/create-msg.ejs");
}
}

async function create(req, res, next) {
try {
const insert = await db.insertMsg(
req.user.id,
req.body.title,
req.body.description
);

insert

res.redirect("/");
} catch (error) {
console.error(error);
next(error);
}
}

module.exports = { getCreateMsgForm, create };







En src/controllers/logInController.js pega el siguiente codigo:




const db = require("../db/query.js");
const passportLocalStrategy = require("../middleware/passportLocalStrategy.js");

async function getLoginForm(req, res) {
if (req.user !== undefined) res.redirect("/");
const messages = await db.getAllmsg();
if (req.isUnauthenticated()) {
res.render("pages/log-in.ejs");
} else {
res.render("pages/index.ejs", { messages, user: req.user });
}
}

function handleForm(req, res, next) {}

module.exports = { getLoginForm, handleForm, passportLocalStrategy };






En src/controllers/memberJoinController.js pega el siguiente codigo:




const db = require("../db/query.js");
// const passportLocalStrategy = require("../middleware/passportLocalStrategy.js");

async function getMemberJoinForm(req, res) {

if (req.isUnauthenticated() || req.user.is_member) {
res.redirect("/");
} else {
res.render("pages/memberJoin.ejs");
}

}

async function confirmSecretAccess(req, res) {
try {
const getSecret = await db.getSecretCode(req.body.secretCode);

if (!req.user.is_member) {
await db.updateStatusMember(req.user.id, req.body.secretCode);
}

if (req.body.secretCode !== getSecret) {
return res.render("pages/memberJoin.ejs", {
incorrect: "secret code incorrect!!!!",
});
}

res.redirect("/");

} catch (error) {
console.error("Error in confirmSecretAccess:", error);
res.status(500).send("Internal Server Error");
}
}


module.exports = { getMemberJoinForm, confirmSecretAccess };






En src/controllers/indexController.js pega el siguiente codigo:




const db = require("../db/query.js");

async function getIndex(req, res) {
const messages = await db.getAllmsg();

if (req.isUnauthenticated()) {
res.render("pages/index.ejs", { messages });
} else {
// console.log(req)
res.render("pages/index.ejs", { user: req.user, messages });
}

}

module.exports = { getIndex };






En src/controllers/logOutController.js pega el siguiente codigo:




async function getLogout(req, res, next) {
req.logout((err) => {
if (err) {
return next(err);
}
res.redirect("/");
});
}
module.exports = { getLogout }






En src/controllers/signUpController.js pega el siguiente codigo:




const pool = require("../db/pool.js");
const bcrypt = require("bcrypt");

async function getSignupForm(req, res) {
if (req.isUnauthenticated()) {
res.render("pages/sign-up-form.ejs");
} else {
res.redirect("/");
}
}

async function create(req, res, next) {
try {
const hashedPassword = await bcrypt.hash(req.body.password, 10);
await pool.query(
"insert into users (username, password, first_name, last_name) values ($1, $2, $3, $4)",
[
req.body.username,
hashedPassword,
req.body.first_name,
req.body.last_name,

]
);
// AUTHENTICATE
res.redirect("/log-in");
} catch (error) {
console.error(error);
next(error);
}


}

module.exports = { getSignupForm, create };









8. Middleware



Pega los siguientes comandos en tu terminal:




mkidr src/middleware/validation
touch src/middleware/passportLocalStrategy.js src/middleware/validation/createMsgValidation.js src/middleware/validation/signUpValidation.js






En src/middleware/passportLocalStrategy.js pega el siguiente codigo:




const pool = require("../db/pool.js");
const passportLocalStrategy = require("passport");
const LocalStrategy = require("passport-local").Strategy;
const bcrypt = require("bcrypt");

passportLocalStrategy.use(
new LocalStrategy(async (username, password, done) => {
try {
const { rows } = await pool.query(
"SELECT * FROM users WHERE username = $1",
[username]
);
const user = rows[0];

if (!user) {
return done(null, false, { message: "Incorrect username" });
}

const match = await bcrypt.compare(password, user.password);
if (!match) {
// passwords do not match!
return done(null, false, { message: "Incorrect password" });
}

// if (user.password !== password) {
// return done(null, false, { message: "Incorrect password" });
// }
return done(null, user);
} catch (err) {
return done(err);
}
})
);

passportLocalStrategy.serializeUser((user, done) => {
done(null, user.id);
});

passportLocalStrategy.deserializeUser(async (id, done) => {
try {
const { rows } = await pool.query("SELECT * FROM users WHERE id = $1", [
id,
]);

const user = rows[0];

done(null, user);
} catch (err) {
done(err);
}
});


module.exports = passportLocalStrategy;







En src/middleware/validation/createMsgValidation.js pega el siguiente codigo:




const { body, oneOf, validationResult } = require("express-validator");


const leastOneErr =
"At least one valid name field (title, description) must be provided";

const validateMsg = () => {
return [
oneOf(
[
body("title")
.trim() /*.isEmpty()*/
.isLength({ max: 500 }),
body("description")
.trim() /*.isEmpty()*/
.isLength({ max: 255 }),
],
{ message: `${leastOneErr}` }
),

(req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
const checkError = errors.array().map((error) => error.msg);

res.status(400).json({
msg: checkError,
});

return;
}
next();
},
];
};

module.exports = { validateMsg };






En src/middleware/validation/signUpValidation.js pega el siguiente codigo:




const { body, oneOf, validationResult } = require("express-validator");


const leastOneErr =
"At least one valid name field (first_name, last_name, or username) must be provided";

const validateUser = () => {
return [
oneOf(
[
body("first_name").trim()/*.isEmpty()*/.isLength({ max: 50 }),
body("last_name").trim()/*.isEmpty()*/.isLength({ max: 50 }),
body("username").trim()/*.isEmpty()*/.isLength({ max: 255 }),
],
{ message: `${leastOneErr}` }
),
body("password")
.trim()
// .isEmpty()
.isLength({ max: 255 })
.withMessage(`Password invalid`),
body("passwordConfirmation")
.trim()
.custom((value, { req }) => {
return value === req.body.password;
})
.withMessage(`password must match`),

(req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
const checkError = errors.array().map((error) => error.msg);

res.status(400).json({
msg: checkError,
});

return;
}
next();
},
];
};

module.exports = { validateUser };










9. Estilos y vistas






mkdir src/views/pages src/views/partials
touch src/views/pages/create-msg.ejs src/views/pages/index.ejs src/views/pages/log-in.ejs src/views/pages/memberJoin.ejs src/views/pages/sign-up-form.ejs src/views/partials/footer.ejs src/views/partials/nav.ejs src/views/sign-up-form.ejs






En src/views/pages/create-msg.ejs pega el siguiente codigo:




<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Create Messages</title>
<link rel="stylesheet" href="css/base.css" />
<link rel="stylesheet" href="css/pages/create-msg.css" />
</head>

<body>
<div class="container">
<div class="login-card">

<h1 class="title">Create Message</h1>

<form action="/create-msg" method="POST">
<div class="input-group">
<label for="title">Title</label>
<input id="title" name="title" placeholder="title" type="text" required />
</div>
<div class="input-group">
<label for="description">Description</label>
<textarea name="description" id="description" required></textarea>
</div>
<div class="btn-form">
<button class="btn" type="submit">Send</button>
</div>

</form>

</div>

</div>

</body>

</html>






En src/views/pages/index.ejs pega el siguiente codigo:




<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Members Only</title>
<link rel="stylesheet" href="css/base.css" />
<link rel="stylesheet" href="css/structure.css" />
<link rel="stylesheet" href="css/pages/index.css" />
<link rel="stylesheet" href="css/pages/container_message.css" />

</head>

<body class="grid-index">
<header>
<%- include("../partials/nav.ejs") %>
</header>
<main>
<div class="title">
<h1>Messages</h1>
<% if (locals.user) { %>
<a href="/create-msg">Create a new message</a>
<% } %>
</div>
<div class="container-msg">

<% messages.forEach(item=> { %>
<dl class="card">
<% if (locals.user && locals.user.is_member != undefined && locals.user.is_member === true ) {%>
<dt class="info-card-span author">
<p>
<%= item.username %>
</p>
<p>
<%= item.date.toDateString() %>
</p>
</dt>

<% } %>
<dt class="info-card-span text">
<p>
<%= item.title %>
</p>
</dt>
<dt class="info-card-span text">
<p>
<%= item.description %>
</p>
</dt>
</dl>
<% }) %>


</div>
<%# if (locals.user) {%>
<%# } %>
</main>
<%# else { %>
<!-- -->
<%#}%>
<%- include("../partials/footer.ejs") %>
</body>

</html>






En src/views/pages/log-in.ejs pega el siguiente codigo:




<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Login</title>
<link rel="stylesheet" href="css/base.css" />
<link rel="stylesheet" href="css/pages/log-in.css" />
</head>

<body>
<div class="container">
<div class="login-card">

<h1 class="title">Log In</h1>

<form action="/log-in" method="POST">
<div class="input-group">
<label for="username">Username</label>
<input id="username" name="username" placeholder="username" type="text" required />
</div>
<div class="input-group">
<label for="password">Password</label>
<input id="password" name="password" placeholder="password" type="password" required />
</div>
<div class="btn-form">
<button class="btn" type="submit">Log In</button>
</div>
<div class="or">or</div>
<div class="sign-up-link">
<p>Are You New? <a href="/sign-up">Create Account</a></p>
</div>
</form>

</div>

</div>

</body>

</html>






En src/views/pages/memberJoin.ejs pega el siguiente codigo:




<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Login</title>
<link rel="stylesheet" href="css/base.css" />
<link rel="stylesheet" href="css/pages/log-in.css" />
</head>

<body>
<div class="container">
<div class="login-card">

<h1 class="title">Log In</h1>

<form action="/log-in" method="POST">
<div class="input-group">
<label for="username">Username</label>
<input id="username" name="username" placeholder="username" type="text" required />
</div>
<div class="input-group">
<label for="password">Password</label>
<input id="password" name="password" placeholder="password" type="password" required />
</div>
<div class="btn-form">
<button class="btn" type="submit">Log In</button>
</div>
<div class="or">or</div>
<div class="sign-up-link">
<p>Are You New? <a href="/sign-up">Create Account</a></p>
</div>
</form>

</div>

</div>

</body>

</html>






En src/views/pages/sign-up-form.ejs pega el siguiente código:




<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Sign Up</title>
<link rel="stylesheet" href="css/base.css" />
<link rel="stylesheet" href="css/pages/sign-up-form.css" />

</head>

<body>
<div id="container">

<div class="photo-container">
<div class="logo">
<img class="img-odin"
src="https://cdn.statically.io/gh/TheOdinProject/curriculum/5f37d43908ef92499e95a9b90fc3cc291a95014c/html_css/project-sign-up-form/odin-lined.png"
alt="odin">
<p>Members Only</p>
</div>
<div class="text-logo">
<p>Photo by
<a href="https://unsplash.com/es/fotos/planta-de-hoja-verde-en-fotografia-de-primer-plano-25xggax4bSA">
Halie West
</a>
on
<a href="https://unsplash.com/es/fotos/planta-de-hoja-verde-en-fotografia-de-primer-plano-25xggax4bSA">
Unsplash
</a>
</p>
</div>

<img src="images/halie-west-25xggax4bSA-unsplash.jpg" alt="img">

</div>
<div class="form-container">
<div class="center">
<form id="form-area" name="form" action="/sign-up" method="POST">
<div class="title">
<h1>Let's do this</h1>

</div>
<div class="input-group">
<label for="first_name">First Name</label>
<input id="first_name" name="first_name" placeholder="first_name" type="text" required />
</div>
<div class="input-group">
<label for="last_name">Last Name</label>
<input id="last_name" name="last_name" placeholder="last_name" type="text" required />
</div>
<div class="input-group">
<label for="username">Username</label>
<input id="username" name="username" placeholder="username" type="text" required />
</div>
<div class="input-group">
<label for="password">Password</label>
<input id="password" name="password" type="password" autocomplete="password"
placeholder="password" aria-describedby="password" required>
</div>
<div class="input-group">

<label for="passwordConfirmation">Confirm Password</label>
<input id="passwordConfirmation" name="passwordConfirmation" type="password" aria-describedby="password confirmation"
placeholder="password Confirmation" required>

<div class="password-requirements">
<p class="requirement hidden error" id="match">* Passwords must match</p>
</div>
</div>
<div class="btn-form">
<button class="btn" type="submit" id="signin">Sign Up</button>

</div>


<div class="or">or</div>
<div class="login-link">
<p>Already have an account? <a href="/log-in">Login</a></p>
</div>


</form>

</div>

</div>
</div>

<script src="js/sign-up-form.js"></script>
</body>

</html>






En src/views/partials/footer.ejs pega el siguiente código:




<footer>
<p>Copyright © github.com/Jose-C0 2025</p>
<a href="https://github.com/Jose-C0" target="_blank">
<img src="images/github-mark.png" alt="https://github.com/Jose-C0" />
</a>
</footer>






En src/views/partials/nav.ejs pega el siguiente código:




<nav>
<div class="container-nav">
<ul class="left-links">
<li><a href="/member-join"><img class="img-odin"
src="https://cdn.statically.io/gh/TheOdinProject/curriculum/5f37d43908ef92499e95a9b90fc3cc291a95014c/html_css/project-sign-up-form/odin-lined.png"
alt="odin"></a></li>
<li><a href="/">Clubhouse</a></li>
</ul>
<ul class="right-links">
<% if(locals.user) { %>
<li><p>
WELCOME BACK <%= locals.user.username %>
</p>
</li>
<li><a href="/log-out"> Log-out </a></li>
<% } else { %>
<li><a href="/log-in"> Log-in </a></li>
<li><a href="/sign-up"> Sign-up </a></li>
<% } %>

</ul>
</div>
</nav>






En src/views/sign-up-form.ejs pega el siguiente código:




<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<title></title>
<link rel="stylesheet" href="css/base.css" />
</head>

<body>
<h1>Sign Up</h1>
<form action="" method="POST">
<label for="username">Username</label>
<input id="username" name="username" placeholder="username" type="text" />
<label for="password">Password</label>
<input id="password" name="password" type="password" />
<button>Sign Up</button>
</form>
</body>

</html>






Así debería verse tu carpeta src:



src

├── app.js

├── controllers

├── db

├── middleware

├── public

├── routes

└── views



Como no tenemos base de datos la aplicación lanzará un error. Haremos eso enseguida





Paso 2 - Dockeriza la aplicación.



Tanto la app como el contenedor están diseñados para funcionar con las variables de entrono. Esto, sumado a que aún no tienes base de datos, hace que sigan apareciendo errores al correr el comando npm start.





1. Crea las variables de entorno:



En la raiz del projecto crea el archivo .env.




touch .env






Pega las variables de entorno en tu archivo .env:




HOSTS_DB="localhost"
PORT_DB="5432"
USER_DB="odin"
PASSWORD_DB="secreto"
DATABASE_DB="odindb"
IP_NODE_SERVER="localhost"
PORT_NODE_SERVER="1234"
PORT_NODE_SERVER_DOCKER="1234"









2. Crea un archivo Dockerfile:



En la raíz del proyecto crea el archivo Dockerfile:




touch Dockerfile






Pega el siguiente código en Dockerfile:




ARG NODE_VERSION=20.13.1

FROM node:${NODE_VERSION}-alpine

WORKDIR /app

COPY package.json .

RUN npm install

COPY . .

CMD npm start






Usa la versión 20.13.1 de Node.js sobre Alpine Linux (imagen liviana).

Define /app como directorio de trabajo dentro del contenedor.

Copia package.json y ejecuta npm install para instalar dependencias.

Copia el resto de los archivos del proyecto al contenedor.

Ejecuta npm start como comando principal al iniciar el contenedor





3. Crea un archivo compose.yaml:



Crea el archivo compose.yaml en la raíz del proyecto:




touch  compose.yaml






Pega el código siguiente en compose.yaml:




services:
app:
container_name: nodejs-authentication-basics
build: .
env_file:
- path: ./.env
ports:
- "${PORT_NODE_SERVER}:1234"
develop:
watch:
- action: sync
path: .
target: /app
ignore:
- node_modules/
- action: rebuild
path: package.json
depends_on:
- postgres_server_test_odin
networks:
- inventory-app-network
- adminer-network

postgres_server_test_odin:
container_name: postgres_server_test_odin
# image: postgres:17.1
build: ./scripts/
healthcheck:
test: ["CMD", "bash", "-c", "./dbIsEmpty.sh"]
start_period: 10s
env_file:
- path: ./.env
environment:
# POSTGRES_HOST_AUTH_METHOD: ${HOSTS_DB}
POSTGRES_SERVER_PORT: ${PORT_DB}
POSTGRES_USER: ${USER_DB}
POSTGRES_PASSWORD: ${PASSWORD_DB}
POSTGRES_DB: ${DATABASE_DB}
ports:
- ${PORT_DB}:${PORT_DB}
volumes:
- postgres-data:/var/lib/postgresql/data
networks:
- adminer-network

adminer:
image: adminer
ports:
- 8080:8080
environment:
ADMINER_DEFAULT_SERVER: postgres_server_test_odin
depends_on:
- postgres_server_test_odin
networks:
- adminer-network

volumes:
postgres-data:

networks:
adminer-network:
name: db_adminer-network
inventory-app-network:






Este docker-compose.yml define tres servicios conectados en red para una aplicación Node.js con base de datos PostgreSQL:



app: contenedor de la app Node.js.

Se construye desde el Dockerfile.

Usa variables de entorno desde .env.

Expone el puerto ${PORT_NODE_SERVER} al host (internamente 1234).

Sincroniza cambios en tiempo real con el host (develop.watch).

Depende del contenedor de PostgreSQL.

Está conectado a dos redes.

postgres_server_test_odin: contenedor de PostgreSQL.

Se construye desde el directorio ./scripts/.

Usa variables de entorno para configuración (usuario, clave, db, puerto).

Incluye un healthcheck que ejecuta ./dbIsEmpty.sh.

Guarda los datos en un volumen (postgres-data).

Conectado a la red adminer-network.

adminer: interfaz web para gestionar la base de datos.

Usa la imagen oficial de Adminer.

Expone el puerto 8080.

Se conecta a PostgreSQL al arrancar.

Comparte red con PostgreSQL.

También se definen dos redes (adminer-network, inventory-app-network) y un volumen persistente para los datos de PostgreSQL.





4. Ejecutar app:



En la terminal, donde mismo esté ubicado tu archivo compose.yaml ejecuta el comando siguiente para iniciar los servicios del contenedor:




docker compose up –-watch






Nota: Presiona ctrl + c para detener los servicios del contenedor.

1. Sofort-Triage & Abwehrmaßnahmen

SOC Incident Playbook: Vulnerability Remediation & Verification
Syntax validiert (0 Fehler)
title: Detect Exploitation - ¿Cómo ejecutar Node.js, PostgreSQL y Express.js en contenedores dentro de un entorno de desarrollo?
id: be5da253-39cb-4559-8d8d-9c1f17eeeee8
status: experimental
description: Automatisch generierte SIEM-Erkennungsregel basierend auf CTI Intelligence
references:
  - https://tsecurity.de/
author: iShareStuff CTI Automated Detection Engine
date: 2026-09-26
logsource:
  category: network_connection
  product: any
detection:
  selection:
      CommandLine|contains:
        - 'exploit'
  condition: selection
falsepositives:
  - Legitime administrative Zugriffe oder Penetrationstests
level: high
tags:
  - attack.initial_access
Syntax validiert (0 Fehler)
rule CTI_Threat_Indicator {
    meta:
        author = "iShareStuff CTI Automated Detection Engine"
        date = "2026-09-26"
        description = "YARA Signature for "
    strings:
        $str = "¿Cómo ejecutar Node.js, Postgr" ascii wide
    condition:
        any of them
}
Syntax validiert (0 Fehler)
index=security sourcetype IN ("cisco:asa", "pan:traffic", "zeek_conn", "suricata", "WinEventLog:Security")
("Cmo ejecutar Nodejs PostgreSQL y Express")
| stats count earliest(_time) as first_seen latest(_time) as last_seen by src_ip, dest_ip, dest_host, signature
| eval first_seen=strftime(first_seen, "%Y-%m-%d %H:%M:%S"), last_seen=strftime(last_seen, "%Y-%m-%d %H:%M:%S")
| sort - count
Syntax validiert (0 Fehler)
message: "*Cmo ejecutar Nodejs PostgreSQL y Express*"
Syntax validiert (0 Fehler)
CommonSecurityLog
| where Message has "Cmo ejecutar Nodejs PostgreSQL y Express"
| summarize EventCount = count(), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated) by SourceIP, DestinationIP, DestinationPort, Activity
| extend DetectionRule = "iShareStuff-CTI-Compiled"
| sort by EventCount desc

2. Cyber Threat Intelligence & Forensik

CTI Threat Relationship Graph3 Knoten / 2 Relationen
CVE / Incident Software MITRE ATT&CK CWE Weakness IoC
🎯
MITRE ATT&CK Matrix Navigator 14 Taktiken
Reconnaissance
-
Resource Development
-
Initial Access
Execution
Persistence
-
Privilege Escalation
Defense Evasion
Credential Access
-
Discovery
-
Lateral Movement
-
Collection
-
Command and Control
Exfiltration
-
Impact
tsecurity.de Cognitive Threat RAG
Fokus-Vektor:

Kognitive Analyse für identifizierte Bedrohung: Erhöhte Bedrohungslage im Bereich ¿Cómo ejecutar Node.js, PostgreSQL y Exp.... Basierend auf 368k Vektor-Korrelationen werden sofortige Isolationsmaßnahmen für betroffene Endpunkte empfohlen.

🛡️ Angriffsfläche & Exposure

Netzwerk/Remote-Zugriff ohne Vorauthentifizierung möglich.

⚡ Empfohlene Sofortmaßnahmen
  • 1. Perimeter-Inspektion: Relevante Portfreigaben und exponierte Endpunkte unverzüglich scannen.
  • 2. Patch-Applikation: Hersteller-Hotfix einspielen oder betroffene Daemons in isolierte DMZ-Segmente überführen.
  • 3. Telemetrie & EDR-Alerts: Prozessaufrufe und Child-Processes auf anomale Shell-Spawns überwachen.
🔗 Semantisch verwandte Zero-Days MariaDB 11.7 VEC
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten ¿Cómo ejecutar Node.js, PostgreSQL y Express.js en contenedores dentro de un entorno de desarrollo?

Thematisch verwandte Begriffe: Cómo, ejecutar, Nodejs, PostgreSQL · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-88003 | InvoicePlane is a self-hosted open source application for managing invoi…
Advisory →
tsecurity.de Icon
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag