How to Install and Configure Docker Compose for a Web App (Nginx + App + Database)
Docker Compose is one of the easiest ways to run a production-style web stack on a VPS or cloud VM: reverse proxy (Nginx) + application + database, all isolated in containers with repeatable configuration.
This guide is written for beginners but follows professional best practices and stays AdSense-safe. It covers: container/image/volume/network concepts, correct Docker + Compose installation, project structure (Dockerfile vs prebuilt images), Compose fundamentals (
services
, ports
, env
, depends_on
), Postgres/MySQL with persistent volumes, Nginx reverse proxy for multi-service, healthchecks/restart policies, logging/backups/safe updates, and common troubleshooting.1) Core Concepts: Container, Image, Volume, Network
Before touching commands, understand these four terms:
Container
A container is a running instance of an image. It’s your app process in a lightweight isolated environment.
Image
An image is the build artifact (blueprint) used to create containers.
Images can be:
pulled from registries (Docker Hub, GHCR) like
postgres:16
built from your own
Dockerfile
Volume
A volume stores data outside the container’s lifecycle. This is critical for databases.
If you delete a database container without a volume, your data can be lost. With a volume, the data persists.
Network
A Docker network lets containers talk to each other by service name (DNS). In Docker Compose, you usually get this automatically.
Example: the app can reach Postgres at host
db
(service name), not localhost
.2) Install Docker and Docker Compose (The Right Way)
Most modern Linux distros support Docker installation via official repos. For VPS/cloud, use Docker’s official packages rather than random scripts from unknown sources.
A) Ubuntu quick approach (common, reliable)
1) Update packages:
sudo apt update
sudo apt upgrade -y
2) Install Docker:
Follow the official Docker docs for Ubuntu (recommended), or your provider’s official instructions.
3) Confirm Docker is installed:
docker --version
sudo systemctl status docker
4) Enable Docker on boot:
sudo systemctl enable --now docker
B) Install Docker Compose
Today Compose is typically included as the Docker CLI plugin:
Check:
docker compose version
If you see a version output, you’re good.
C) (Optional) Run Docker without sudo
Add your user to the docker group:
sudo usermod -aG docker $USER
Log out and log back in, then test:
docker ps
Security note: Docker group membership is effectively root-level power. Only grant it to trusted users.
3) Project Structure: Dockerfile vs Ready-Made Image
A standard structure for a Compose-based web app:
myproject/
docker-compose.yml
.env
nginx/
default.conf
app/
Dockerfile
package.json (or requirements.txt, etc.)
src/...
When you use a Dockerfile
Use a
Dockerfile
when:you have your own application code
you need custom dependencies
you want repeatable builds
When you use a prebuilt image
Use prebuilt images when:
the service is standard infrastructure (Postgres/MySQL/Redis/Nginx)
you don’t need custom builds
In most stacks:
Nginx: image from Docker Hub + custom config file mounted
Database: official image + volume
App: built via Dockerfile (your code)
4) Docker Compose File Basics (services, ports, env, depends_on)
A Compose file defines services and how they run together.
Key fields you’ll use:
services:
list of containersbuild:
build from Dockerfileimage:
run a published imageports:
publish container ports to host (public access)environment:
set environment variablesenv_file:
load environment variables from .env
volumes:
persist data or mount configdepends_on:
start order (not readiness)healthcheck:
define when a service is “healthy”restart:
auto-restart behavior5) Example Stack: Nginx + App + Postgres (Production-Style)
Below is a practical Compose setup you can adapt.
A) Create .env
In
myproject/.env
:APP_PORT=3000
POSTGRES_DB=appdb
POSTGRES_USER=appuser
POSTGRES_PASSWORD=change_this_strong_password
# Used by app to connect
DATABASE_URL=postgres://appuser:change_this_strong_password@db:5432/appdb
Do not commit
.env
to public repos.B) App Dockerfile example (Node.js)
In
myproject/app/Dockerfile
:FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "server.js"]
Your Node app should listen on
0.0.0.0:3000
inside the container.C) Nginx reverse proxy config
In
myproject/nginx/default.conf
:server {
listen 80;
server_name _;
# Basic security headers (optional but helpful)
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options SAMEORIGIN always;
add_header Referrer-Policy strict-origin-when-cross-origin always;
location / {
proxy_pass http://app:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# For websockets (if your app needs it)
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
Notice
proxy_pass http://app:3000;
— app
is the Compose service name.D) docker-compose.yml (Nginx + App + Postgres + healthchecks)
In
myproject/docker-compose.yml
:services:
nginx:
image: nginx:1.26-alpine
container_name: nginx_proxy
ports:
- "80:80"
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
app:
condition: service_started
restart: unless-stopped
app:
build: ./app
container_name: app_service
env_file:
- ./.env
environment:
- DATABASE_URL=${DATABASE_URL}
depends_on:
db:
condition: service_healthy
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
interval: 10s
timeout: 3s
retries: 5
db:
image: postgres:16
container_name: postgres_db
environment:
- POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
volumes:
- pg_data:/var/lib/postgresql/data
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 10s
timeout: 3s
retries: 5
volumes:
pg_data:
Notes on this setup
depends_on
controls startup order, but healthcheck makes it more reliable by waiting for DB health before app starts.You need your app to implement an endpoint like
/health
returning 200 OK
.6) Alternative: Using MySQL Instead of Postgres
If you want MySQL:
db:
image: mysql:8.4
environment:
- MYSQL_DATABASE=appdb
- MYSQL_USER=appuser
- MYSQL_PASSWORD=change_this_strong_password
- MYSQL_ROOT_PASSWORD=change_this_root_password
volumes:
- mysql_data:/var/lib/mysql
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h localhost -u appuser -p$MYSQL_PASSWORD --silent"]
interval: 10s
timeout: 5s
retries: 10
And update your app’s DB connection string accordingly.
7) Reverse Proxy Nginx for Multi-Service (Multiple Apps / Routes)
If you want to expose multiple internal services behind one domain:
/api
→ API service/
→ frontend service/admin
→ admin serviceExample Nginx routes:
location /api/ {
proxy_pass http://api:4000/;
}
location /admin/ {
proxy_pass http://admin:5000/;
}
location / {
proxy_pass http://frontend:3000/;
}
This pattern keeps only port 80/443 public and isolates internal ports.
8) Restart Policy, Healthcheck, and “depends_on” Best Practices
Restart policy
Common safe choice:
restart: unless-stopped
for long-running servicesAvoid restart loops masking real issues. Always check logs if a container keeps restarting.
Healthcheck
Use healthchecks to detect readiness and failure:
DB:
pg_isready
or mysqladmin ping
App: call
localhost
endpoint inside containerdepends_on
Great for startup order
Not a full “wait until ready” solution unless combined with healthchecks (Compose v2 supports conditions in many environments)
9) Logging, Backups, and Safe Updates
A) Logging
View logs:
docker compose logs -f
docker compose logs -f app
Check running containers:
docker compose ps
B) Backing up volumes (database)
Postgres backup example
Run a one-time dump:
docker exec -t postgres_db pg_dump -U appuser appdb > backup.sql
For full cluster backup:
docker exec -t postgres_db pg_dumpall -U appuser > backup_all.sql
Best practice: store backups off-server (object storage) and test restores.
C) Safe update workflow (minimal risk)
1) Pull latest images:
docker compose pull
2) Rebuild app if needed:
docker compose build
3) Restart safely:
docker compose up -d
4) Remove unused images:
docker image prune
Pin image versions
Avoid relying on
latest
for critical services. Prefer:postgres:16
nginx:1.26-alpine
This prevents surprise breaking changes.
10) Common Troubleshooting (Port Conflicts, Permissions, Env Errors)
Problem 1: Port conflict (80 already in use)
Error example: “bind: address already in use”
Fix:
Stop other web servers on host (Nginx/Apache installed outside Docker), or
Change host port mapping:
ports:
- "8080:80"
Then access via
http://YOUR_SERVER_IP:8080
.Check what’s using port 80:
sudo ss -tulpn | grep :80
Problem 2: Permission issues with volumes
Symptoms:
DB fails to start
“Permission denied” when writing to mounted folders
Fixes:
Prefer named volumes for databases (like
pg_data:
), not bind mountsIf you bind mount a host directory, ensure correct ownership/permissions
Avoid running containers as root unnecessarily
Problem 3: Wrong environment variables / app can’t connect to DB
Common mistakes:
Using
localhost
inside container (wrong)Incorrect credentials
DB not ready yet
Correct approach:
DB host should be the service name:
db
Example:
postgres://user:pass@db:5432/appdb
Check env inside container:
docker exec -it app_service env | grep DATABASE
Problem 4: App healthcheck failing
If
/health
doesn’t exist, your app will show unhealthy.Fix:
implement
/health
endpointor adjust healthcheck to match your app’s route
Problem 5: Container keeps restarting
Check logs:
docker logs app_service --tail 200
docker logs postgres_db --tail 200
Usually caused by:
bad config
missing env vars
DB migration errors
port already bound
app exiting immediately (wrong start command)
Conclusion
Docker Compose is one of the best tools for deploying a web app stack on a VPS/cloud because it gives you:
consistent environments (containers/images)
safe persistent storage (volumes)
clean service-to-service communication (networks)
repeatable deployments (
docker compose up -d
)For a strong foundation, focus on:
1) correct installation of Docker + Compose
2) a clean project structure (Dockerfile for app, official images for infra)
3) persistent volumes for databases
4) Nginx reverse proxy to expose only ports 80/443
5) healthchecks + restart policies for resilience
6) safe updates, logs, and a real backup plan
This setup scales surprisingly far for small-to-medium production workloads—and it’s a solid stepping stone toward more advanced orchestration later

Komentar
Posting Komentar