Docker Compose Guide: Multi-Container Application Management
Define and manage multi-container applications with Docker Compose: service dependencies, environment variables, health checks, scaling, and production deployment. Comprehensive guide.
Build CI/CD pipelines with GitHub Actions: Docker build and push, SSH server deployment, test automation, secrets management, and workflow optimization.
GitHub Actions is a powerful CI/CD platform that automatically tests, builds, and deploys code changes to your server. In this guide we'll build a production-ready pipeline from scratch.
GitHub Actions is an automation platform integrated into GitHub repositories:
GitHub Actions workflow files are stored in YAML format in the .github/workflows/ directory.
# .github/workflows/deploy.yml
name: CI/CD Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
deploy:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Deploy
run: echo "Deploy steps here"
on:
# Push to branch
push:
branches: [main, develop]
paths:
- 'src/**'
- 'package.json'
# Pull request
pull_request:
branches: [main]
types: [opened, synchronize, reopened]
# Scheduled run (cron)
schedule:
- cron: '0 2 * * *' # Every night at 02:00
# Manual trigger
workflow_dispatch:
inputs:
environment:
description: 'Deploy environment'
required: true
default: 'staging'
type: choice
options: [staging, production]
# When another workflow completes
workflow_run:
workflows: ["Build"]
types: [completed]
jobs:
build:
runs-on: ubuntu-latest
env:
NODE_ENV: test
timeout-minutes: 30
strategy:
matrix:
node-version: [18, 20, 22]
os: [ubuntu-latest, windows-latest]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
jobs:
docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: username/myapp
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=sha,prefix=sha-
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and Push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and Push (GHCR)
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ghcr.io/${{ github.repository }}:latest
deploy:
needs: docker
runs-on: ubuntu-latest
environment: production
steps:
- name: Deploy via SSH
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: ${{ secrets.SERVER_PORT }}
script: |
cd /root/myapp
git pull origin main
docker compose pull
docker compose up -d --remove-orphans
docker image prune -f
echo "Deploy completed: $(date)"
- name: Docker Compose deploy
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
# Pull new image
docker pull ghcr.io/${{ github.repository }}:latest
# Zero-downtime deploy
docker compose -f /root/app/docker-compose.yml up -d \
--no-deps \
--build \
--remove-orphans
# Clean old images
docker image prune -af --filter "until=24h"
# Health check
sleep 10
curl -f http://localhost:3000/health || exit 1
GitHub Secrets securely stores sensitive information:
steps:
- name: Application configuration
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
JWT_SECRET: ${{ secrets.JWT_SECRET }}
API_KEY: ${{ secrets.API_KEY }}
run: |
echo "Configuration loaded"
# Secrets are never printed to logs, automatically masked
Required secrets:
SERVER_HOST: Server IP addressSERVER_USER: SSH usernameSSH_PRIVATE_KEY: SSH private keyDOCKERHUB_USERNAME: Docker Hub usernameDOCKERHUB_TOKEN: Docker Hub access token - name: Cache Node.js dependencies
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: npm ci (cached)
run: npm ci
# Docker layer cache
- name: Build and Push
uses: docker/build-push-action@v5
with:
cache-from: type=gha
cache-to: type=gha,mode=max
jobs:
test:
strategy:
fail-fast: false
matrix:
node: [18, 20]
os: [ubuntu-latest, macos-latest]
include:
- node: 20
os: ubuntu-latest
coverage: true
exclude:
- node: 18
os: macos-latest
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm ci
- run: npm test
- name: Coverage report
if: matrix.coverage
run: npm run coverage
jobs:
deploy-production:
runs-on: ubuntu-latest
environment:
name: production
url: https://example.com
steps:
- name: Production deploy
run: echo "Deploying to production"
In GitHub under Settings > Environments > production:
name: Full CI/CD
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm run lint
- run: npm test -- --coverage
- name: Upload coverage
uses: codecov/codecov-action@v4
build:
needs: test
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
outputs:
image-tag: ${{ steps.meta.outputs.version }}
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- uses: docker/build-push-action@v5
with:
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max
deploy:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
environment:
name: production
url: https://example.com
steps:
- uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:main
docker compose -f /root/app/docker-compose.yml up -d
docker image prune -f
By setting up a fully automated CI/CD pipeline with GitHub Actions, you can improve code quality, reduce deployment errors, and speed up your development process. With secrets management, cache optimization, and environment protection rules, you get a production-grade pipeline.
It's completely free and unlimited for public repositories. Private repositories get 2,000 free minutes per month. If this limit is exceeded on the GitHub Free plan, workflows pause. GitHub Pro and Team plans include more minutes.
Generate a key pair on your local machine with 'ssh-keygen -t ed25519 -C "github-actions"'. Add the public key (~/.ssh/id_ed25519.pub) to ~/.ssh/authorized_keys on your server. Save the private key (~/.ssh/id_ed25519) in your GitHub repo under Settings > Secrets as SSH_PRIVATE_KEY.
'needs: test' means this job won't run until the 'test' job completes successfully. Use 'needs: [test, build]' for multiple dependencies. If a job fails, dependent jobs are automatically cancelled.
Yes, use 'on.push.paths' to watch specific files or directories. For example 'paths: ["src/**", "package.json"]' triggers the workflow only when these files change. Use 'paths-ignore' to exclude specific files.
Docker build cache prevents rebuilding unchanged layers. 'cache-from: type=gha' uses GitHub Actions cache. This can reduce build time by 50-80%. It makes a big difference especially for dependency installation steps (npm install, pip install).
They add an approval mechanism for critical environments like production. With 'Required reviewers', deployment cannot proceed without approval from specific people. 'Wait timer' sets a waiting period before deployment. This prevents accidental deployments to production.
Define and manage multi-container applications with Docker Compose: service dependencies, environment variables, health checks, scaling, and production deployment. Comprehensive guide.
Ansible installation, inventory file, playbook writing, role structure, variables, encrypted vault, ad-hoc commands, and server configuration automation. Step-by-step guide.
Caddy web server installation, Caddyfile configuration, automatic Let's Encrypt SSL, reverse proxy, static site hosting, PHP-FPM integration, and performance settings.