Task 7: Docker Compose with To Do App
In this section, we will learn how to use Docker Compose to manage multi-container applications (frontend, backend, database).
Project Challange
Deskripsi: Sebuah startup ingin membangun aplikasi berbasis web dengan arsitektur berikut:
- Frontend: Menggunakan Nginx.
- Backend: API sederhana berbasis Python.
- Database: PostgreSQL untuk menyimpan data aplikasi.
- Networking: Backend dan database harus saling terhubung dengan jaringan.
- Volume: Database PostgreSQL memerlukan penyimpanan data yang persisten.
Tugas Peserta:
- Buat Docker Compose file untuk menjalankan semua service.
- Pastikan Nginx dapat diakses melalui browser.
- Gunakan Docker Volume untuk menyimpan data PostgreSQL.
- Pastikan semua container saling terhubung melalui jaringan.
Notes
Ada beberapa hal yang saya ganti dari project challange ini namun tanpa mengurangi esensi dari project ini. Yaitu:
- Frontend: Saya menggunakan html+css+js sederhana untuk frontend yang akan berinteraksi dengan RestAPI.
- Backend: Saya menggunakan Rust sebagai backendnya. Saya menggunakan actix-web sebagai frameworknya dan projectnya mengenai todo app sederhana.
Source Code
Full source code bisa dilihat di Github
0. Architecture
architecture of the project. source: my own
1. Docker Compose Explanation
Here, i will just explain the docker-compose.yml file that i created for this project. We have 3 services in this docker-compose file:
- PostgreSQL: The database service.
- App: The backend service.
- Frontend: The frontend service.
service: postgres
postgres:
image: postgres:16-alpine
container_name: todo_app_postgres
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: todo_db
ports:
- "5432:5432" # (1)
volumes:
- ./tmp/postgres:/var/lib/postgresql/data # (2)
restart: unless-stopped # (3)
networks:
- app-network # (4)
- Expose PostgreSQL Port: Port 5432 of the PostgreSQL container is exposed to the host machine, allowing external access to the database.
- Persistent Storage: The PostgreSQL data is stored in a local directory (
./tmp/postgres
) to ensure data persistence even if the container is removed. It means also, if we made some changes it will be saved in the local directory, also if we do some changes in the local directory, it will be reflected in the container. Be careful! - Restart Policy: The container will
restart-unless
it is explicitly stopped, ensuring high availability. - Network Configuration: The PostgreSQL container is connected to a custom bridge network (
app-network
), allowing communication with other containers.
service: app
app:
image: todo-app:latest
build:
context: ./todo-app
dockerfile: Dockerfile
ports:
- "8080:8080"
environment:
- DATABASE_URL=postgres://postgres:postgres@postgres:5432/todo_db # (1)
depends_on:
- postgres # (2)
networks:
- app-network
- Environment Variables: The database connection string is passed as an environment variable to the application container. The url like this:
postgres://postgres:postgres@postgres:5432/todo_db
is the connection string to connect to the PostgreSQL database. The format is like this:postgres://<username>:<password>@<hostname>:<port>/<database_name>
. Thehostname
is the name of the service in the docker-compose file, in this case, it ispostgres
. - Service Dependency: The application container depends on the PostgreSQL container, ensuring that the database is started before the application.
service: frontend
frontend:
image: node:18
working_dir: /app
volumes:
- ./frontend:/app
command: npx http-server -p 8081 # (1)
ports:
- "8081:8081"
depends_on:
- app # (2)
networks:
- app-network
- Frontend Service: A simple Node.js server is used to serve the frontend files, allowing access to the web application.
- Service Dependency: The frontend container depends on the application container, ensuring that the backend is started before the frontend.
networks
A custom bridge network is created with name app-network
to allow communication between containers. Bridge networks are the default network type in Docker, allowing containers to communicate with each other. So we can use the service name as the hostname to connect to other services.
Here is the full docker-compose.yml file:
docker-compose.yml
version: '3.8' # version of docker-compose file format
services:
postgres:
image: postgres:16-alpine
container_name: todo_app_postgres
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: todo_db
ports:
- "5432:5432"
volumes:
- ./tmp/postgres:/var/lib/postgresql/data
restart: unless-stopped
networks:
- app-network
app:
image: todo-app:latest
build:
context: ./todo-app
dockerfile: Dockerfile
ports:
- "8080:8080"
environment:
- DATABASE_URL=postgres://postgres:postgres@postgres:5432/todo_db
depends_on:
- postgres
networks:
- app-network
frontend:
image: node:18
working_dir: /app
volumes:
- ./frontend:/app
command: npx http-server -p 8081
ports:
- "8081:8081"
depends_on:
- app
networks:
- app-network
networks:
app-network:
driver: bridge
2. Run the Docker Compose
docker-compose
really makes our life easier. to run this docker-compose file, just run this command:
The container will be build by pull each image from the docker hub and build the image from the Dockerfile in the todo-app
directory.
-d
flag is used to run the containers in detached mode, allowing them to run in the background.--build
flag is used to build the images before starting the containers.
3. Result
3.1 Frontend
logs of the frontend container. source: my own
3.2 Backend
logs of the backend container. source: my own
3.3 Database
logs of the database container. source: my own
4. You can do it too!
- Access the frontend at http://localhost:8081
Notes
if you find this useful, please give me a on my repo
Thanks!