Final Docker multi-service application

This commit is contained in:
root 2026-04-01 00:38:48 +02:00
commit 99bc49225b
14 changed files with 511 additions and 0 deletions

112
README.md Normal file
View File

@ -0,0 +1,112 @@
# Docker Multi-Service Notes Application
## Project Overview
This project is a multi-container web application deployed using Docker. It demonstrates how different services can work together using containerization.
The application allows users to:
- Add and delete notes
- Upload files
- View and download uploaded files
---
## Architecture
The application consists of the following services:
**Frontend (Nginx)**
Serves static HTML, CSS, and JavaScript files. Accessible via browser.
**Backend (Node.js + Express)**
Handles API requests and manages notes and file uploads.
**MongoDB**
Stores notes data and uses a persistent Docker volume.
**Mongo Express**
Provides a web interface to view and manage database data.
---
## Technologies Used
- Docker and Docker Compose
- Node.js with Express
- MongoDB (NoSQL database)
- Nginx (web server)
- HTML, CSS, and JavaScript
---
## Service Ports
| Service | Port |
|---------------|------|
| Frontend | 8080 |
| Backend | 3000 |
| Mongo Express | 8081 |
---
## Persistent Storage
The application uses Docker volumes to preserve data across container restarts:
- `mongo-data` stores database records
- `uploads/` stores user-uploaded files
Data is retained even after stopping the application.
---
## How to Run
### 1. Prepare the application
```bash
./prepare-app.sh
```
### 2. Start the application
```bash
./start-app.sh
```
Once running, open the following in your browser:
- Application: `http://147.232.204.210:8080`
- Database UI: `http://147.232.204.210/:8081`
### 3. Stop the application
```bash
./stop-app.sh
```
### 4. Remove the application
```bash
./remove-app.sh
```
---
## Application Workflow
1. The user interacts with the frontend in a browser.
2. The frontend sends API requests to the backend.
3. The backend processes the requests and communicates with MongoDB.
4. MongoDB stores or retrieves the relevant data.
5. Mongo Express provides a separate interface to inspect database content directly.
---
## Features
- Create and delete notes
- Upload files
- View and download uploaded files
- Persistent storage via Docker volumes

6
backend/Dockerfile Normal file
View File

@ -0,0 +1,6 @@
FROM node:18
WORKDIR /app
COPY package.json .
RUN npm install
COPY . .
CMD ["node", "app.js"]

58
backend/Dockerfile.save Normal file
View File

@ -0,0 +1,58 @@
GNU nano 7.2 backend/Dockerfile
[ New File ]
^G Help ^O Write Out ^W Where Is ^K Cut ^T Execute ^C Location M-U Undo M-A Set Mark M-] To Bracket M-Q Previous ^B Back ^◂ Prev Word
^X Exit ^R Read File ^\ Replace ^U Paste ^J Justify ^/ Go To Line M-E Redo M-6 Copy ^Q Where Was M-W Next ^F Forward ^▸ Next Word

57
backend/app.js Normal file
View File

@ -0,0 +1,57 @@
const express = require("express");
const mongoose = require("mongoose");
const multer = require("multer");
const cors = require("cors");
const fs = require("fs");
const app = express();
app.use(express.json());
app.use(cors());
app.use("/uploads", express.static("uploads"));
mongoose.connect("mongodb://mongo:27017/notes");
const Note = mongoose.model("Note", { text: String });
const storage = multer.diskStorage({
destination: "uploads/",
filename: (req, file, cb) => {
cb(null, Date.now() + "-" + file.originalname);
}
});
const upload = multer({ storage });
app.get("/notes", async (req, res) => {
res.json(await Note.find());
});
app.post("/notes", async (req, res) => {
const note = new Note(req.body);
await note.save();
res.json(note);
});
app.post("/upload", upload.single("file"), (req, res) => {
res.json({ message: "Uploaded" });
});
app.listen(3000, () => console.log("Backend running"));
app.get("/", (req, res) => {
res.send("Backend is running");
});
app.delete("/notes/:id", async (req, res) => {
await Note.findByIdAndDelete(req.params.id);
res.json({ message: "Deleted" });
});
app.get("/files", (req, res) => {
fs.readdir("uploads/", (err, files) => {
if (err) return res.status(500).send("Error");
res.json(files);
});
});

10
backend/package.json Normal file
View File

@ -0,0 +1,10 @@
{
"name": "notes-app",
"version": "1.0.0",
"dependencies": {
"express": "^4.18.2",
"mongoose": "^7.0.0",
"multer": "^1.4.5-lts.1",
"cors": "^2.8.5"
}
}

38
docker-compose.yml Normal file
View File

@ -0,0 +1,38 @@
version: "3.8"
services:
frontend:
image: nginx
ports:
- "8080:80"
volumes:
- ./frontend:/usr/share/nginx/html
backend:
build: ./backend
ports:
- "3000:3000"
volumes:
- ./uploads:/app/uploads
depends_on:
- mongo
restart: always
mongo:
image: mongo
volumes:
- mongo-data:/data/db
restart: always
mongo-express:
image: mongo-express
ports:
- "8081:8081"
depends_on:
- mongo
environment:
ME_CONFIG_MONGODB_SERVER: mongo
restart: always
volumes:
mongo-data:

214
frontend/index.html Normal file
View File

@ -0,0 +1,214 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Notes Application</title>
<style>
body {
font-family: Arial, sans-serif;
background: #f4f6f9;
margin: 0;
padding: 0;
}
.container {
width: 500px;
margin: 50px auto;
background: white;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
h1 {
text-align: center;
color: #333;
}
input {
padding: 10px;
margin: 5px 0;
width: 100%;
border-radius: 5px;
border: 1px solid #ccc;
}
button {
padding: 10px;
margin: 5px 0;
border-radius: 5px;
border: none;
cursor: pointer;
}
button {
background: #007bff;
color: white;
cursor: pointer;
border: none;
}
button:hover {
background: #0056b3;
}
.upload-btn {
background: #28a745;
}
.upload-btn:hover {
background: #1e7e34;
}
ul {
list-style: none;
padding: 0;
}
li {
background: #f1f1f1;
margin: 5px 0;
padding: 10px;
border-radius: 5px;
position: relative;
}
.delete-btn {
background: red;
color: white;
padding: 5px 10px;
border-radius: 5px;
width: auto; /* VERY IMPORTANT */
}
.delete-btn:hover {
background: darkred;
}
.file-link {
margin-left: 10px;
font-size: 12px;
}
.section {
margin-top: 20px;
}
.note-item {
display: flex;
justify-content: space-between;
align-items: center;
background: #f1f1f1;
margin: 8px 0;
padding: 10px;
border-radius: 5px;
}
.delete-btn {
background: red;
color: white;
border: none;
padding: 5px 10px;
border-radius: 5px;
cursor: pointer;
}
</style>
</head>
<body>
<div class="container">
<h1>Notes Application</h1>
<div class="section">
<input id="note" placeholder="Write a note..." />
<button onclick="addNote()">Add Note</button>
</div>
<ul id="list"></ul>
<div class="section">
<h3>Upload File</h3>
<input type="file" id="file"/>
<button class="upload-btn" onclick="uploadFile()">Upload File</button>
</div>
<h3>Files</h3>
<ul id="files"></ul>
</div>
<script>
const API = "http://" + location.hostname + ":3000";
async function loadNotes() {
const res = await fetch(API + "/notes");
const data = await res.json();
document.getElementById("list").innerHTML =
data.map(n => `
<li class="note-item">
<span>${n.text}</span>
<button class="delete-btn" onclick="deleteNote('${n._id}')">Delete</button>
</li>
`).join("");
}
async function deleteNote(id) {
await fetch(API + "/notes/" + id, { method: "DELETE" });
loadNotes();
}
async function addNote() {
const text = document.getElementById("note").value;
if (!text) return;
await fetch(API + "/notes", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({ text })
});
document.getElementById("note").value = "";
loadNotes();
}
async function uploadFile() {
const fileInput = document.getElementById("file");
if (!fileInput.files[0]) return;
const formData = new FormData();
formData.append("file", fileInput.files[0]);
await fetch(API + "/upload", {
method: "POST",
body: formData
});
alert("File uploaded successfully");
loadFiles();
}
async function loadFiles() {
const res = await fetch(API + "/files");
const files = await res.json();
document.getElementById("files").innerHTML =
files.map(f => `
<li>
${f}
<a class="file-link" href="http://${location.hostname}:3000/uploads/${f}" target="_blank">Download</a>
</li>
`).join("");
}
loadNotes();
loadFiles();
</script>
</body>
</html>

5
prepare-app.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/bash
docker network create app-network 2>/dev/null
docker volume create mongo-data
docker volume create uploads
docker-compose build

4
remove-app.sh Executable file
View File

@ -0,0 +1,4 @@
#!/bin/bash
docker-compose down -v
docker system prune -f

5
start-app.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/bash
docker-compose up -d
echo "App running at: http://147.232.204.210:8080"
echo "Mongo Express: http://147.232.204.210:8081"

2
stop-app.sh Executable file
View File

@ -0,0 +1,2 @@
#!/bin/bash
docker-compose down

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 993 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB