From 365cb6692e10731a5ad3af0c34225fd1950d7bf8 Mon Sep 17 00:00:00 2001 From: ryanv Date: Sat, 7 Mar 2026 03:01:43 +0000 Subject: [PATCH] init --- .dockerignore | 6 +++ .env.example | 5 +++ .gitignore | 3 ++ Dockerfile | 11 ++++++ Jenkinsfile | 87 ++++++++++++++++++++++++++++++++++++++++++++ README.md | 91 ++++++++++++++++++++++++++++++++++++++++++++++ app.py | 26 +++++++++++++ docker-compose.yml | 11 ++++++ 8 files changed, 240 insertions(+) create mode 100644 .dockerignore create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 Jenkinsfile create mode 100644 README.md create mode 100644 app.py create mode 100644 docker-compose.yml 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