commit 365cb6692e10731a5ad3af0c34225fd1950d7bf8 Author: ryanv Date: Sat Mar 7 03:01:43 2026 +0000 init diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..70c95f6 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +.git +.gitignore +Jenkinsfile +README.md +.env* +*.md diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..c0347d7 --- /dev/null +++ b/.env.example @@ -0,0 +1,5 @@ +# Copy to .env on deploy host (or set in Jenkins) +# These are passed to docker-compose during deployment +DOCKER_REGISTRY=docker.io +DOCKER_IMAGE=myorg/myapp +IMAGE_TAG=latest diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..83a921e --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.env +*.pyc +__pycache__/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f9f8469 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +# Simple example app - a minimal Python web server +FROM python:3.11-slim + +WORKDIR /app + +# Copy application +COPY app.py . + +EXPOSE 8080 + +CMD ["python", "-u", "app.py"] diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..e6a8059 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,87 @@ +pipeline { + agent any + + environment { + // Configure these in Jenkins or as pipeline parameters + DOCKER_REGISTRY = 'docker.io' + DOCKER_IMAGE = 'myorg/myapp' // e.g., username/repo for Docker Hub + DEPLOY_HOST = 'deploy-server.example.com' + DEPLOY_USER = 'deploy' + DEPLOY_PATH = '/opt/myapp' + GIT_REPO_URL = 'https://github.com/myorg/jenkins-docker-deploy-example.git' + } + + options { + buildDiscarder(logRotator(numToKeepStr: '10')) + timeout(time: 30, unit: 'MINUTES') + } + + stages { + stage('Build Docker Image') { + steps { + script { + env.IMAGE_TAG = env.BRANCH_NAME == 'main' ? 'latest' : "${env.BUILD_NUMBER}-${env.GIT_COMMIT.take(7)}" + env.FULL_IMAGE = "${env.DOCKER_REGISTRY}/${env.DOCKER_IMAGE}:${env.IMAGE_TAG}" + } + echo "Building image: ${env.FULL_IMAGE}" + sh """ + docker build -t ${env.FULL_IMAGE} . + """ + } + } + + stage('Push to Registry') { + steps { + withCredentials([usernamePassword( + credentialsId: 'docker-registry-credentials', + usernameVariable: 'DOCKER_USER', + passwordVariable: 'DOCKER_PASS' + )]) { + sh """ + echo \$DOCKER_PASS | docker login -u \$DOCKER_USER --password-stdin ${env.DOCKER_REGISTRY} + docker push ${env.FULL_IMAGE} + """ + } + } + } + + stage('Deploy via SSH') { + steps { + sshagent(credentials: ['deploy-ssh-key']) { + sh """ + ssh -o StrictHostKeyChecking=no ${env.DEPLOY_USER}@${env.DEPLOY_HOST} << 'DEPLOY_EOF' + set -e + cd ${env.DEPLOY_PATH} || mkdir -p ${env.DEPLOY_PATH} && cd ${env.DEPLOY_PATH} + + # Clone or pull the repo (contains docker-compose.yml) + if [ -d .git ]; then + git fetch origin + git reset --hard origin/${env.BRANCH_NAME} + else + git clone ${env.GIT_REPO_URL} . + fi + + # Set the image tag for this deployment + export IMAGE_TAG=${env.IMAGE_TAG} + export DOCKER_REGISTRY=${env.DOCKER_REGISTRY} + export DOCKER_IMAGE=${env.DOCKER_IMAGE} + + # Pull latest image and deploy + docker compose pull + docker compose up -d +DEPLOY_EOF + """ + } + } + } + } + + post { + success { + echo "Deployment successful! Image: ${env.FULL_IMAGE}" + } + failure { + echo "Deployment failed!" + } + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..eb9c6fa --- /dev/null +++ b/README.md @@ -0,0 +1,91 @@ +# Jenkins Docker Deploy Example + +Example repository demonstrating a Jenkins pipeline that: + +1. **Builds** a Docker image +2. **Pushes** the image to a container registry (Docker Hub, etc.) +3. **SSHs** to a deployment machine +4. **Clones** (or pulls) this repo to get `docker-compose.yml` +5. **Deploys** with `docker compose up -d` + +## Repository Structure + +``` +. +├── Jenkinsfile # Pipeline definition +├── Dockerfile # Application image +├── docker-compose.yml # Deployment compose (pulled by deploy host) +├── app.py # Minimal Python web app +└── README.md +``` + +## Prerequisites + +### Jenkins + +- **Docker** installed and Jenkins user in `docker` group +- **Pipeline** and **SSH Agent** plugins +- **Git** for cloning + +### Jenkins Credentials + +Create these in **Manage Jenkins → Credentials**: + +| ID | Type | Purpose | +|----|------|---------| +| `docker-registry-credentials` | Username/Password | Docker Hub or registry login | +| `deploy-ssh-key` | SSH Username with private key | SSH to deploy host | + +### Deploy Host + +- Docker and Docker Compose installed +- SSH access for the deploy user +- If using a **private registry**: run `docker login` on the deploy host (or add to the pipeline) + +## Configuration + +Edit the `environment` block in `Jenkinsfile`: + +```groovy +environment { + DOCKER_REGISTRY = 'docker.io' // or your registry (ghcr.io, etc.) + DOCKER_IMAGE = 'myorg/myapp' // e.g., username/repo + DEPLOY_HOST = 'deploy-server.example.com' + DEPLOY_USER = 'deploy' + DEPLOY_PATH = '/opt/myapp' // Where to clone & run compose + GIT_REPO_URL = 'https://github.com/myorg/jenkins-docker-deploy-example.git' +} +``` + +## Pipeline Stages + +1. **Build** – `docker build` with tag from branch (e.g. `latest` for main, `123-abc1234` for others) +2. **Push** – `docker push` to registry using stored credentials +3. **Deploy** – SSH to host, clone/pull repo, run `docker compose up -d` + +## First-Time Deploy Host Setup + +On the deploy host: + +```bash +# Create deploy directory +sudo mkdir -p /opt/myapp +sudo chown deploy:deploy /opt/myapp + +# Ensure deploy user can run docker (add to docker group) +sudo usermod -aG docker deploy +``` + +## Manual Test + +```bash +# Build and run locally +docker build -t myapp:test . +docker run -p 8080:8080 myapp:test +# Visit http://localhost:8080 +``` + +## Branch Behavior + +- **main** → image tag `latest` +- **other branches** → image tag `{BUILD_NUMBER}-{GIT_SHA}` diff --git a/app.py b/app.py new file mode 100644 index 0000000..659f7e9 --- /dev/null +++ b/app.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +"""Minimal web app for deployment example.""" +import os +from http.server import HTTPServer, BaseHTTPRequestHandler + +PORT = int(os.environ.get("PORT", 8080)) +VERSION = os.environ.get("VERSION", "dev") + + +class Handler(BaseHTTPRequestHandler): + def do_GET(self): + self.send_response(200) + self.send_header("Content-type", "text/html") + self.end_headers() + self.wfile.write( + f"

Hello from Docker!

Version: {VERSION}

".encode() + ) + + def log_message(self, format, *args): + print(f"[{self.log_date_time_string()}] {format % args}") + + +if __name__ == "__main__": + server = HTTPServer(("", PORT), Handler) + print(f"Server running on port {PORT}") + server.serve_forever() diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..ea1c397 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,11 @@ +# Docker Compose for deployment +# IMAGE_TAG is set by Jenkins during deployment (e.g., latest, or git commit SHA) +services: + app: + image: ${DOCKER_REGISTRY:-docker.io}/${DOCKER_IMAGE:-myapp}:${IMAGE_TAG:-latest} + container_name: jenkins-deploy-app + ports: + - "8080:8080" + environment: + - VERSION=${IMAGE_TAG:-latest} + restart: unless-stopped