Automating the Architect's Workflow
Orchestrating Multiple Containers
Our NestJS application is now in a container. But a real application has multiple parts:
Starting them manually with `docker run` is complex. You have to manage networks, volumes for data persistence, and environment variables, leading to long, error-prone commands.
Docker Compose is a tool for defining and running multi-container Docker applications. You use a single YAML file to configure all your application's services.
Analogy: Instead of telling each musician in an orchestra what to play individually, you give a single sheet of music (`docker-compose.yml`) to a conductor (Docker Compose) who orchestrates the entire performance.
version: '3.8' # Specifies the Compose file format version
services:
# Our NestJS API service
api:
build: . # Build the image from the Dockerfile in the current directory
ports:
- "3000:3000" # Map host port 3000 to container port 3000
environment:
- DATABASE_HOST=db
depends_on:
- db # Tells Compose to start the 'db' service before this one
# Our PostgreSQL database service
db:
image: postgres:15-alpine # Use a pre-built image from Docker Hub
environment:
- POSTGRES_USER=admin
- POSTGRES_PASSWORD=secret
- POSTGRES_DB=my_app
volumes:
- pgdata:/var/lib/postgresql/data # Persist the database data
volumes:
pgdata: # Defines the named volume
Decoupling Code from Configuration
You should NEVER commit secrets like database passwords, API keys, or JWT secrets directly into your source code or `docker-compose.yml` file.
Doing so is a massive security vulnerability. Your code is meant to be shared; your configuration is not.
We use environment variables to inject configuration into our application at runtime. This decouples the code from the configuration.
For local development, we create a `.env` file to store these variables.
This `.env` file MUST be added to your `.gitignore` and `.dockerignore`.
# .env file
DATABASE_USER=admin
DATABASE_PASSWORD=secret
JWT_SECRET=my_super_long_and_random_jwt_secret
Docker Compose automatically looks for and loads a file named `.env` in the same directory.
# docker-compose.yml
services:
db:
image: postgres:15-alpine
environment:
# These variables are automatically substituted from the .env file
- POSTGRES_USER=${DATABASE_USER}
- POSTGRES_PASSWORD=${DATABASE_PASSWORD}
# ...
api:
build: .
environment:
- JWT_SECRET=${JWT_SECRET}
# ...
In production, these variables would be set securely by your cloud provider or deployment system, not from a file.
The Automated Assembly Line
CI/CD is about creating a fast, reliable, and automated pipeline from code to production.
GitHub Actions is a CI/CD platform built directly into GitHub. It allows you to automate your workflows in response to events (like a `push` or `pull_request`).
Workflows are defined in YAML files inside a special `.github/workflows` directory in your repository.
For our NestJS project, a typical CI workflow will perform the following jobs:
If any of these steps fail, the entire workflow fails, and the developer is notified immediately.
`.github/workflows/ci.yml`
name: NestJS CI Pipeline
on: [push, pull_request]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: 18
- name: Install dependencies
run: npm install
- name: Run linter
run: npm run lint
- name: Run tests
run: npm run test
Your task is to create a complete `docker-compose.yml` file that orchestrates your NestJS API and a PostgreSQL database, using an `.env` file for configuration.