Task Manager Docker web application
3-service Docker app: Nginx frontend, Flask REST API backend, PostgreSQL database. Includes lifecycle scripts (prepare, start, stop, remove), docker-compose.yaml, and documentation.
This commit is contained in:
commit
8dc74a1062
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
docs/superpowers/
|
||||
207
README.md
Normal file
207
README.md
Normal file
@ -0,0 +1,207 @@
|
||||
# Task Manager - Docker Web Application
|
||||
|
||||
A simple task manager web application deployed as a multi-container Docker system. Users can create, complete, and delete tasks through a web interface.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Linux** with Docker installed (Docker Engine 20.10+)
|
||||
- **Docker Compose** v2 (optional, for `docker compose` deployment)
|
||||
- Ports **80** must be available on the host machine
|
||||
|
||||
## Application Description
|
||||
|
||||
The Task Manager is a web-based CRUD application for managing personal tasks. It consists of three services working together:
|
||||
|
||||
- A **frontend** web interface served by Nginx where users interact with the application
|
||||
- A **backend** REST API built with Flask (Python) that handles business logic
|
||||
- A **PostgreSQL database** that stores task data persistently
|
||||
|
||||
Users can:
|
||||
- Add new tasks
|
||||
- Mark tasks as completed (toggle checkbox)
|
||||
- Delete tasks
|
||||
- View all tasks in a list sorted by creation date
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Browser (port 80)
|
||||
|
|
||||
v
|
||||
+--------+ +-------+ +------------+
|
||||
| Nginx | ----> | Flask | ----> | PostgreSQL |
|
||||
| :80 | API | :5000 | SQL | :5432 |
|
||||
+--------+ proxy +-------+ +------------+
|
||||
static REST API persistent
|
||||
files (gunicorn) volume
|
||||
```
|
||||
|
||||
## Virtual Networks
|
||||
|
||||
| Network Name | Driver | Purpose |
|
||||
|-------------------|--------|------------------------------------------------------|
|
||||
| taskapp-network | bridge | Connects all 3 containers so they can communicate |
|
||||
|
||||
All containers are attached to `taskapp-network`. Only Nginx exposes a port (80) to the host. Flask and PostgreSQL are accessible only within the Docker network.
|
||||
|
||||
## Named Volumes
|
||||
|
||||
| Volume Name | Mount Point | Purpose |
|
||||
|----------------|------------------------------------|----------------------------------|
|
||||
| taskapp-pgdata | /var/lib/postgresql/data (in db) | Persists database data across container restarts and stops |
|
||||
|
||||
Stopping and restarting the application preserves all task data thanks to this volume.
|
||||
|
||||
## Containers
|
||||
|
||||
### 1. taskapp-nginx (Frontend)
|
||||
|
||||
- **Image:** Custom, built from `nginx:alpine`
|
||||
- **Port:** 80 (host) -> 80 (container)
|
||||
- **Role:** Serves static HTML/CSS/JS files and reverse-proxies `/api/*` requests to the Flask backend
|
||||
- **Restart policy:** `unless-stopped`
|
||||
- **Configuration:** Custom `nginx.conf` with `proxy_pass` directive for API routing
|
||||
|
||||
### 2. taskapp-flask (Backend)
|
||||
|
||||
- **Image:** Custom, built from `python:3.12-slim`
|
||||
- **Port:** 5000 (internal only, not exposed to host)
|
||||
- **Role:** REST API server handling task CRUD operations
|
||||
- **Restart policy:** `unless-stopped`
|
||||
- **Configuration:** Environment variables for database connection (`DB_HOST`, `DB_NAME`, `DB_USER`, `DB_PASSWORD`)
|
||||
- **WSGI server:** Gunicorn with 2 workers
|
||||
- **Auto-initialization:** Creates the `tasks` table on startup if it does not exist
|
||||
|
||||
### 3. taskapp-db (Database)
|
||||
|
||||
- **Image:** `postgres:15` (from Docker Hub)
|
||||
- **Port:** 5432 (internal only, not exposed to host)
|
||||
- **Role:** Stores task data (id, title, completed status, creation timestamp)
|
||||
- **Restart policy:** `unless-stopped`
|
||||
- **Configuration:** Environment variables (`POSTGRES_DB`, `POSTGRES_USER`, `POSTGRES_PASSWORD`)
|
||||
- **Volume:** `taskapp-pgdata` mounted at `/var/lib/postgresql/data`
|
||||
|
||||
## Container Configuration Details
|
||||
|
||||
- **Nginx** is configured via `frontend/nginx.conf` which sets up static file serving and reverse proxy rules
|
||||
- **Flask** reads database credentials from environment variables passed at container runtime
|
||||
- **PostgreSQL** is configured via standard Postgres environment variables; data is stored on a named volume
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
### Prepare the Application
|
||||
|
||||
Build images and create Docker resources:
|
||||
|
||||
```bash
|
||||
./prepare-app.sh
|
||||
```
|
||||
|
||||
### Start the Application
|
||||
|
||||
Run all containers:
|
||||
|
||||
```bash
|
||||
./start-app.sh
|
||||
```
|
||||
|
||||
Output:
|
||||
```
|
||||
Starting app...
|
||||
App is running!
|
||||
The app is available at http://localhost:80
|
||||
```
|
||||
|
||||
### View in Web Browser
|
||||
|
||||
Open your web browser and navigate to:
|
||||
|
||||
```
|
||||
http://localhost:80
|
||||
```
|
||||
|
||||
You will see the Task Manager interface where you can add, complete, and delete tasks.
|
||||
|
||||
### Stop the Application
|
||||
|
||||
Stop all containers (data is preserved):
|
||||
|
||||
```bash
|
||||
./stop-app.sh
|
||||
```
|
||||
|
||||
### Remove the Application
|
||||
|
||||
Remove all containers, images, networks, and volumes:
|
||||
|
||||
```bash
|
||||
./remove-app.sh
|
||||
```
|
||||
|
||||
### Alternative: Docker Compose
|
||||
|
||||
You can also use Docker Compose instead of the shell scripts:
|
||||
|
||||
```bash
|
||||
# Start
|
||||
docker compose up -d --build
|
||||
|
||||
# Stop (preserves data)
|
||||
docker compose down
|
||||
|
||||
# Remove everything including volumes
|
||||
docker compose down -v --rmi all
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|-------------------|------------------------|
|
||||
| GET | /api/tasks | List all tasks |
|
||||
| POST | /api/tasks | Create a new task |
|
||||
| PUT | /api/tasks/:id | Toggle task completion |
|
||||
| DELETE | /api/tasks/:id | Delete a task |
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
.
|
||||
├── backend/
|
||||
│ ├── Dockerfile # Python/Flask image definition
|
||||
│ ├── requirements.txt # Python dependencies
|
||||
│ └── app.py # Flask REST API application
|
||||
├── frontend/
|
||||
│ ├── Dockerfile # Nginx image definition
|
||||
│ ├── nginx.conf # Nginx configuration (static files + reverse proxy)
|
||||
│ ├── index.html # Main HTML page
|
||||
│ ├── style.css # Styles
|
||||
│ └── app.js # Frontend JavaScript (fetch API calls)
|
||||
├── docker-compose.yaml # Docker Compose configuration
|
||||
├── prepare-app.sh # Script to build images and create resources
|
||||
├── start-app.sh # Script to start all containers
|
||||
├── stop-app.sh # Script to stop all containers
|
||||
├── remove-app.sh # Script to remove all traces of the app
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## Sources
|
||||
|
||||
- [Docker Documentation](https://docs.docker.com/)
|
||||
- [Nginx Docker Image](https://hub.docker.com/_/nginx)
|
||||
- [PostgreSQL Docker Image](https://hub.docker.com/_/postgres)
|
||||
- [Python Docker Image](https://hub.docker.com/_/python)
|
||||
- [Flask Documentation](https://flask.palletsprojects.com/)
|
||||
- [Gunicorn Documentation](https://gunicorn.org/)
|
||||
- [Nginx Reverse Proxy Guide](https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/)
|
||||
|
||||
## Use of Artificial Intelligence
|
||||
|
||||
This application was designed and implemented with the assistance of **Claude** (Anthropic), an AI assistant. Claude was used for:
|
||||
|
||||
- Designing the application architecture and service composition
|
||||
- Writing application source code (Python/Flask backend, HTML/CSS/JS frontend)
|
||||
- Writing Dockerfiles and Docker Compose configuration
|
||||
- Writing shell scripts for application lifecycle management
|
||||
- Writing this documentation
|
||||
|
||||
**AI agent used:** Claude Opus 4.6 (Anthropic) via Claude Code CLI
|
||||
12
backend/Dockerfile
Normal file
12
backend/Dockerfile
Normal file
@ -0,0 +1,12 @@
|
||||
FROM python:3.12-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY app.py .
|
||||
|
||||
EXPOSE 5000
|
||||
|
||||
CMD ["sh", "-c", "python -c 'from app import init_db; init_db()' && gunicorn --bind 0.0.0.0:5000 --workers 2 app:app"]
|
||||
110
backend/app.py
Normal file
110
backend/app.py
Normal file
@ -0,0 +1,110 @@
|
||||
import os
|
||||
import time
|
||||
import psycopg2
|
||||
from psycopg2.extras import RealDictCursor
|
||||
from flask import Flask, request, jsonify
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
DB_CONFIG = {
|
||||
"host": os.environ.get("DB_HOST", "db"),
|
||||
"database": os.environ.get("DB_NAME", "taskapp"),
|
||||
"user": os.environ.get("DB_USER", "taskapp"),
|
||||
"password": os.environ.get("DB_PASSWORD", "taskapp123"),
|
||||
}
|
||||
|
||||
|
||||
def get_db():
|
||||
return psycopg2.connect(**DB_CONFIG, cursor_factory=RealDictCursor)
|
||||
|
||||
|
||||
def init_db():
|
||||
"""Wait for PostgreSQL and create the tasks table if it doesn't exist."""
|
||||
for attempt in range(30):
|
||||
try:
|
||||
conn = get_db()
|
||||
cur = conn.cursor()
|
||||
cur.execute("""
|
||||
CREATE TABLE IF NOT EXISTS tasks (
|
||||
id SERIAL PRIMARY KEY,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
completed BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
""")
|
||||
conn.commit()
|
||||
cur.close()
|
||||
conn.close()
|
||||
print("Database initialized.")
|
||||
return
|
||||
except psycopg2.OperationalError:
|
||||
print(f"Waiting for database... (attempt {attempt + 1}/30)")
|
||||
time.sleep(2)
|
||||
raise RuntimeError("Could not connect to the database after 30 attempts.")
|
||||
|
||||
|
||||
@app.route("/api/tasks", methods=["GET"])
|
||||
def get_tasks():
|
||||
conn = get_db()
|
||||
cur = conn.cursor()
|
||||
cur.execute("SELECT * FROM tasks ORDER BY created_at DESC")
|
||||
tasks = cur.fetchall()
|
||||
cur.close()
|
||||
conn.close()
|
||||
return jsonify(tasks)
|
||||
|
||||
|
||||
@app.route("/api/tasks", methods=["POST"])
|
||||
def create_task():
|
||||
data = request.get_json()
|
||||
title = data.get("title", "").strip()
|
||||
if not title:
|
||||
return jsonify({"error": "Title is required"}), 400
|
||||
|
||||
conn = get_db()
|
||||
cur = conn.cursor()
|
||||
cur.execute(
|
||||
"INSERT INTO tasks (title) VALUES (%s) RETURNING *",
|
||||
(title,),
|
||||
)
|
||||
task = cur.fetchone()
|
||||
conn.commit()
|
||||
cur.close()
|
||||
conn.close()
|
||||
return jsonify(task), 201
|
||||
|
||||
|
||||
@app.route("/api/tasks/<int:task_id>", methods=["PUT"])
|
||||
def update_task(task_id):
|
||||
conn = get_db()
|
||||
cur = conn.cursor()
|
||||
cur.execute(
|
||||
"UPDATE tasks SET completed = NOT completed WHERE id = %s RETURNING *",
|
||||
(task_id,),
|
||||
)
|
||||
task = cur.fetchone()
|
||||
conn.commit()
|
||||
cur.close()
|
||||
conn.close()
|
||||
if task is None:
|
||||
return jsonify({"error": "Task not found"}), 404
|
||||
return jsonify(task)
|
||||
|
||||
|
||||
@app.route("/api/tasks/<int:task_id>", methods=["DELETE"])
|
||||
def delete_task(task_id):
|
||||
conn = get_db()
|
||||
cur = conn.cursor()
|
||||
cur.execute("DELETE FROM tasks WHERE id = %s RETURNING id", (task_id,))
|
||||
deleted = cur.fetchone()
|
||||
conn.commit()
|
||||
cur.close()
|
||||
conn.close()
|
||||
if deleted is None:
|
||||
return jsonify({"error": "Task not found"}), 404
|
||||
return jsonify({"result": "ok"})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
init_db()
|
||||
app.run(host="0.0.0.0", port=5000)
|
||||
3
backend/requirements.txt
Normal file
3
backend/requirements.txt
Normal file
@ -0,0 +1,3 @@
|
||||
flask==3.1.1
|
||||
psycopg2-binary==2.9.10
|
||||
gunicorn==23.0.0
|
||||
47
docker-compose.yaml
Normal file
47
docker-compose.yaml
Normal file
@ -0,0 +1,47 @@
|
||||
services:
|
||||
db:
|
||||
image: postgres:15
|
||||
container_name: taskapp-db
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_DB: taskapp
|
||||
POSTGRES_USER: taskapp
|
||||
POSTGRES_PASSWORD: taskapp123
|
||||
volumes:
|
||||
- taskapp-pgdata:/var/lib/postgresql/data
|
||||
networks:
|
||||
- taskapp-network
|
||||
|
||||
flask:
|
||||
build: ./backend
|
||||
image: taskapp-backend
|
||||
container_name: taskapp-flask
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
DB_HOST: db
|
||||
DB_NAME: taskapp
|
||||
DB_USER: taskapp
|
||||
DB_PASSWORD: taskapp123
|
||||
depends_on:
|
||||
- db
|
||||
networks:
|
||||
- taskapp-network
|
||||
|
||||
nginx:
|
||||
build: ./frontend
|
||||
image: taskapp-frontend
|
||||
container_name: taskapp-nginx
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "80:80"
|
||||
depends_on:
|
||||
- flask
|
||||
networks:
|
||||
- taskapp-network
|
||||
|
||||
volumes:
|
||||
taskapp-pgdata:
|
||||
|
||||
networks:
|
||||
taskapp-network:
|
||||
driver: bridge
|
||||
8
frontend/Dockerfile
Normal file
8
frontend/Dockerfile
Normal file
@ -0,0 +1,8 @@
|
||||
FROM nginx:alpine
|
||||
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
COPY index.html /usr/share/nginx/html/
|
||||
COPY style.css /usr/share/nginx/html/
|
||||
COPY app.js /usr/share/nginx/html/
|
||||
|
||||
EXPOSE 80
|
||||
72
frontend/app.js
Normal file
72
frontend/app.js
Normal file
@ -0,0 +1,72 @@
|
||||
const taskList = document.getElementById("task-list");
|
||||
const taskForm = document.getElementById("task-form");
|
||||
const taskInput = document.getElementById("task-input");
|
||||
const emptyMsg = document.getElementById("empty-msg");
|
||||
|
||||
async function fetchTasks() {
|
||||
const res = await fetch("/api/tasks");
|
||||
const tasks = await res.json();
|
||||
renderTasks(tasks);
|
||||
}
|
||||
|
||||
function renderTasks(tasks) {
|
||||
taskList.innerHTML = "";
|
||||
emptyMsg.style.display = tasks.length === 0 ? "block" : "none";
|
||||
|
||||
tasks.forEach(function (task) {
|
||||
const li = document.createElement("li");
|
||||
li.className = "task-item" + (task.completed ? " completed" : "");
|
||||
|
||||
const checkbox = document.createElement("input");
|
||||
checkbox.type = "checkbox";
|
||||
checkbox.className = "task-checkbox";
|
||||
checkbox.checked = task.completed;
|
||||
checkbox.addEventListener("change", function () {
|
||||
toggleTask(task.id);
|
||||
});
|
||||
|
||||
const title = document.createElement("span");
|
||||
title.className = "task-title";
|
||||
title.textContent = task.title;
|
||||
|
||||
const deleteBtn = document.createElement("button");
|
||||
deleteBtn.className = "task-delete";
|
||||
deleteBtn.textContent = "\u00D7";
|
||||
deleteBtn.title = "Delete task";
|
||||
deleteBtn.addEventListener("click", function () {
|
||||
deleteTask(task.id);
|
||||
});
|
||||
|
||||
li.appendChild(checkbox);
|
||||
li.appendChild(title);
|
||||
li.appendChild(deleteBtn);
|
||||
taskList.appendChild(li);
|
||||
});
|
||||
}
|
||||
|
||||
taskForm.addEventListener("submit", async function (e) {
|
||||
e.preventDefault();
|
||||
const title = taskInput.value.trim();
|
||||
if (!title) return;
|
||||
|
||||
await fetch("/api/tasks", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ title: title }),
|
||||
});
|
||||
|
||||
taskInput.value = "";
|
||||
fetchTasks();
|
||||
});
|
||||
|
||||
async function toggleTask(id) {
|
||||
await fetch("/api/tasks/" + id, { method: "PUT" });
|
||||
fetchTasks();
|
||||
}
|
||||
|
||||
async function deleteTask(id) {
|
||||
await fetch("/api/tasks/" + id, { method: "DELETE" });
|
||||
fetchTasks();
|
||||
}
|
||||
|
||||
fetchTasks();
|
||||
21
frontend/index.html
Normal file
21
frontend/index.html
Normal file
@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Task Manager</title>
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Task Manager</h1>
|
||||
<form id="task-form">
|
||||
<input type="text" id="task-input" placeholder="Enter a new task..." required>
|
||||
<button type="submit">Add</button>
|
||||
</form>
|
||||
<ul id="task-list"></ul>
|
||||
<p id="empty-msg" class="empty">No tasks yet. Add one above!</p>
|
||||
</div>
|
||||
<script src="/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
20
frontend/nginx.conf
Normal file
20
frontend/nginx.conf
Normal file
@ -0,0 +1,20 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
large_client_header_buffers 4 32k;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://flask:5000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
}
|
||||
120
frontend/style.css
Normal file
120
frontend/style.css
Normal file
@ -0,0 +1,120 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
background: #f0f2f5;
|
||||
color: #1a1a2e;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-top: 60px;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 520px;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#task-form {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
#task-input {
|
||||
flex: 1;
|
||||
padding: 12px 16px;
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
outline: none;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
#task-input:focus {
|
||||
border-color: #4a6cf7;
|
||||
}
|
||||
|
||||
#task-form button {
|
||||
padding: 12px 24px;
|
||||
background: #4a6cf7;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
#task-form button:hover {
|
||||
background: #3a5ce5;
|
||||
}
|
||||
|
||||
#task-list {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.task-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 14px 16px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 8px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.task-item.completed .task-title {
|
||||
text-decoration: line-through;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.task-checkbox {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
accent-color: #4a6cf7;
|
||||
}
|
||||
|
||||
.task-title {
|
||||
flex: 1;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.task-delete {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #e74c3c;
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
opacity: 0.6;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.task-delete:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.empty {
|
||||
text-align: center;
|
||||
color: #888;
|
||||
font-size: 14px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
19
prepare-app.sh
Normal file
19
prepare-app.sh
Normal file
@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
echo "Preparing app..."
|
||||
|
||||
# Build Docker images
|
||||
echo "Building backend image..."
|
||||
docker build -t taskapp-backend ./backend
|
||||
|
||||
echo "Building frontend image..."
|
||||
docker build -t taskapp-frontend ./frontend
|
||||
|
||||
# Create network (ignore error if it already exists)
|
||||
echo "Creating network..."
|
||||
docker network create taskapp-network 2>/dev/null || true
|
||||
|
||||
# Create named volume (ignore error if it already exists)
|
||||
echo "Creating volume..."
|
||||
docker volume create taskapp-pgdata 2>/dev/null || true
|
||||
|
||||
echo "App prepared successfully."
|
||||
17
remove-app.sh
Normal file
17
remove-app.sh
Normal file
@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
echo "Removing app..."
|
||||
|
||||
# Stop and remove containers
|
||||
docker stop taskapp-nginx taskapp-flask taskapp-db 2>/dev/null
|
||||
docker rm taskapp-nginx taskapp-flask taskapp-db 2>/dev/null
|
||||
|
||||
# Remove images
|
||||
docker rmi taskapp-backend taskapp-frontend 2>/dev/null
|
||||
|
||||
# Remove network
|
||||
docker network rm taskapp-network 2>/dev/null
|
||||
|
||||
# Remove volume
|
||||
docker volume rm taskapp-pgdata 2>/dev/null
|
||||
|
||||
echo "App removed."
|
||||
39
start-app.sh
Normal file
39
start-app.sh
Normal file
@ -0,0 +1,39 @@
|
||||
#!/bin/bash
|
||||
echo "Starting app..."
|
||||
|
||||
# Start PostgreSQL
|
||||
echo "Starting database..."
|
||||
docker run -d \
|
||||
--name taskapp-db \
|
||||
--network taskapp-network \
|
||||
--restart unless-stopped \
|
||||
-e POSTGRES_DB=taskapp \
|
||||
-e POSTGRES_USER=taskapp \
|
||||
-e POSTGRES_PASSWORD=taskapp123 \
|
||||
-v taskapp-pgdata:/var/lib/postgresql/data \
|
||||
postgres:15
|
||||
|
||||
# Start Flask backend
|
||||
echo "Starting backend..."
|
||||
docker run -d \
|
||||
--name taskapp-flask \
|
||||
--network taskapp-network \
|
||||
--restart unless-stopped \
|
||||
-e DB_HOST=taskapp-db \
|
||||
-e DB_NAME=taskapp \
|
||||
-e DB_USER=taskapp \
|
||||
-e DB_PASSWORD=taskapp123 \
|
||||
taskapp-backend
|
||||
|
||||
# Start Nginx frontend
|
||||
echo "Starting frontend..."
|
||||
docker run -d \
|
||||
--name taskapp-nginx \
|
||||
--network taskapp-network \
|
||||
--restart unless-stopped \
|
||||
-p 80:80 \
|
||||
taskapp-frontend
|
||||
|
||||
echo ""
|
||||
echo "App is running!"
|
||||
echo "The app is available at http://localhost:80"
|
||||
7
stop-app.sh
Normal file
7
stop-app.sh
Normal file
@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
echo "Stopping app..."
|
||||
|
||||
docker stop taskapp-nginx taskapp-flask taskapp-db 2>/dev/null
|
||||
docker rm taskapp-nginx taskapp-flask taskapp-db 2>/dev/null
|
||||
|
||||
echo "App stopped."
|
||||
Loading…
Reference in New Issue
Block a user