GEMVC Documentation

Complete guide to building modern, secure PHP applications with GEMVC framework

🚀 Comprehensive Guide: Deploying a Dockerized Application on a VPS with Traefik and Automatic SSL

This guide provides a complete, step-by-step walkthrough for setting up a secure, modern, and maintainable deployment environment for any containerized application.

Core Concepts

  • VPS (Virtual Private Server): Your own private server space on the internet.
  • Docker: A platform to package your application and its dependencies into a "container," which can run anywhere.
  • Docker Compose: A tool for defining and running multi-container Docker applications. We use it to manage our services.
  • Traefik: A modern reverse proxy. It automatically discovers our running application containers and routes traffic to them.
  • Let's Encrypt: A free service that provides SSL certificates. Traefik will automatically get and renew these for us, enabling `https://` for our domains.

Part 1: Initial Server & Domain Setup

Prerequisites

  1. A VPS running a modern Linux distribution (like Ubuntu 24.04).
  2. A registered domain name (e.g., `your-cool-app.com`).
  3. The DNS "A" record for your domain (and any subdomains) pointed to your VPS's public IP address.

Step 1: Install Docker and Docker Compose

First, connect to your VPS via SSH. Then, install the Docker engine and Docker Compose plugin.

Terminal
# Update package lists
sudo apt-get update

# Install prerequisite packages
sudo apt-get install -y ca-certificates curl

# Add Docker's official GPG key
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# Add the Docker repository to Apt sources
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# Update package lists again with the new repository
sudo apt-get update

# Install Docker Engine and Docker Compose
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
            

Part 2: Setting Up the Core Infrastructure (Traefik & Portainer)

We will create a foundational "infra" stack that runs Traefik (our reverse proxy) and Portainer (a web UI for managing Docker).

Step 2: Create the Directory Structure

Organizing our stacks into folders keeps everything clean.

Terminal
# Create a main directory for all your container stacks
mkdir -p /root/stacks/infra

# Navigate into the new directory
cd /root/stacks/infra
            

Step 3: Create the Traefik Docker Compose File

Create a `docker-compose.yml` file for Traefik.

Terminal
nano /root/stacks/infra/docker-compose.yml

Paste the following content into the file. Remember to replace `your-email@example.com`, `traefik.your-domain.com`, and `portainer.your-domain.com`. You will also need to generate a `htpasswd` for the dashboard and replace the placeholder.

Terminal
version: '3.8'

services:
  traefik:
    image: "traefik:v3.0"
    container_name: "traefik"
    command:
      - "--api.dashboard=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.mytlschallenge.acme.tlschallenge=true"
      - "--certificatesresolvers.mytlschallenge.acme.email=your-email@example.com"
      - "--certificatesresolvers.mytlschallenge.acme.storage=/letsencrypt/acme.json"
      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "./letsencrypt:/letsencrypt"
    networks:
      - shared-net
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.traefik-dashboard.rule=Host(`traefik.your-domain.com`)"
      - "traefik.http.routers.traefik-dashboard.entrypoints=websecure"
      - "traefik.http.routers.traefik-dashboard.tls.certresolver=mytlschallenge"
      - "traefik.http.routers.traefik-dashboard.service=api@internal"
      - "traefik.http.routers.traefik-dashboard.middlewares=auth"
      - "traefik.http.middlewares.auth.basicauth.users=admin:$$apr1$$...$$" # Replace with generated htpasswd

  portainer:
    image: "portainer/portainer-ce:latest"
    container_name: "portainer"
    restart: unless-stopped
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock"
      - "portainer_data:/data"
    networks:
      - shared-net
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.portainer.rule=Host(`portainer.your-domain.com`)"
      - "traefik.http.routers.portainer.entrypoints=websecure"
      - "traefik.http.services.portainer.loadbalancer.server.port=9000"
      - "traefik.http.routers.portainer.tls.certresolver=mytlschallenge"

networks:
  shared-net:
    name: shared-net

volumes:
  portainer_data:
            
Tip: To generate the `htpasswd` needed for the `basicauth.users` middleware, you can use an online generator or install `apache2-utils` (`sudo apt-get install apache2-utils`) and run `htpasswd -nb admin your-secure-password`.

Step 4: Start the Infrastructure Stack

Terminal
# From the /root/stacks/infra directory
docker compose up -d

Part 3: Deploying Your Application

Step 5: Create a Directory for Your App

Terminal
mkdir -p /root/stacks/app/my-awesome-app
cd /root/stacks/app/my-awesome-app
            

Step 6: Create the Application's Docker Compose File

Create a `docker-compose.yml` in this new directory.

Terminal
nano /root/stacks/app/my-awesome-app/docker-compose.yml

Paste and customize the following template:

Terminal
version: '3.8'

services:
  my-awesome-app-service:
    image: your-dockerhub-username/your-app-image:1.0.0
    container_name: my-awesome-app
    restart: unless-stopped
    networks:
      - shared-net
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.my-awesome-app.rule=Host(`app.your-domain.com`)"
      - "traefik.http.routers.my-awesome-app.entrypoints=websecure"
      - "traefik.http.routers.my-awesome-app.tls.certresolver=mytlschallenge"
      - "traefik.http.services.my-awesome-app.loadbalancer.server.port=8080"

networks:
  shared-net:
    external: true
            

Step 7: Create the Environment File (Required)

Tip: This step is mandatory. Your application service will fail to start without a correctly configured `.env` file next to your `docker-compose.yml`.

Create an `.env` file to store your application's secrets and environment variables.

Terminal
nano /root/stacks/app/my-awesome-app/.env

Add your variables. For example:

Terminal
DATABASE_USER=myuser
API_SECRET_KEY=supersecret

Next, ensure your `docker-compose.yml` references this file by adding `env_file: [".env"]` to your application service definition.

Finally, set strict permissions on the file so only the owner can read and write it.

Terminal
chmod 600 /root/stacks/app/my-awesome-app/.env

Step 8: Start Your Application

Terminal
# From the /root/stacks/app/my-awesome-app directory
docker compose up -d

Part 4: Creating a CI/CD Update Workflow (Optional)

Step 9: Create a GitHub Actions Workflow

On your local machine, create the file `.github/workflows/docker-publish.yml` in your project repo.

Terminal
name: CI - Build and Push Docker Image

on:
  release:
    types: [published]

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v3
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2
      - name: Log in to Docker Hub
        uses: docker/login-action@v2
        with:
          username: $
          password: $
      - name: Build and push Docker image
        uses: docker/build-push-action@v4
        with:
          context: .
          push: true
          tags: |
            $/your-app-name:latest
            $/your-app-name:$
            

Step 10: Add Secrets to GitHub

In your GitHub repository, go to `Settings` > `Secrets and variables` > `Actions`. Add two secrets:

  • `DOCKERHUB_USERNAME`
  • `DOCKERHUB_TOKEN`

Step 11: Create a Dedicated Restart Script on the Server (Optional)

Terminal
mkdir -p /root/scripts
nano /root/scripts/restart_my-awesome-app.sh

Paste the following:

Terminal
#!/bin/bash
set -e

APP_DIR="/root/stacks/app/my-awesome-app"

echo "--- Stopping and removing the my-awesome-app stack... ---"
cd "$APP_DIR" && docker compose down

echo "--- Pulling the latest image... ---"
cd "$APP_DIR" && docker compose pull

echo "--- Starting the my-awesome-app stack... ---"
cd "$APP_DIR" && docker compose up -d

echo "--- Update complete. Verifying status... ---"
docker ps --filter "name=my-awesome-app"
            

Make it executable:

Terminal
chmod +x /root/scripts/restart_my-awesome-app.sh

Part 5: The Full Update Process

  1. Local Machine: Push code changes to GitHub.
  2. GitHub: Create a new Release. This triggers the action to build and push the new Docker image.
  3. Server: After the action completes, SSH into your server.
  4. Server: Update the image tag in `/root/stacks/app/my-awesome-app/docker-compose.yml`.
  5. Server: Run the update script: `/root/scripts/restart_my-awesome-app.sh`.

Your updated application is now live.