├── .gitignore ├── README.md ├── docker-compose.yml ├── env.template ├── imgs ├── jenkins-admin-user.png ├── jenkins-authorization-matrix.png ├── jenkins-github-oauth.png ├── jenkins-is-ready.png ├── jenkins-landing-page.png ├── jenkins-plugins-select.png ├── jenkins-unlock.png └── jenkins-url.png ├── jenkins ├── Dockerfile └── docker-entrypoint.sh ├── nginx ├── default.conf └── prefix_jenkins.conf └── ssl ├── README.md ├── chain.pem ├── fullchain.pem └── privkey.pem /.gitignore: -------------------------------------------------------------------------------- 1 | # environment file 2 | .env 3 | 4 | # jenkins files 5 | jenkins_home 6 | 7 | # logging output 8 | logs 9 | 10 | # macOS and IDE 11 | .DS_Store 12 | .idea 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jenkins LTS - Docker 2 | 3 | Notes on [Jenkins LTS](https://www.jenkins.io/download/lts/#ji-toolbar) as a docker deployment orchestrated by Docker Compose. (version denoted at the time of writing this document) 4 | 5 | - Use the LTS version of Jenkins (v2.319.2) 6 | - Use Nginx as the web server (v1) 7 | - Include self-signed SSL certificate (Let's Encrypt localhost format) 8 | 9 | **DISCLAIMER: The code herein may not be up to date nor compliant with the most recent package and/or security notices. The frequency at which this code is reviewed and updated is based solely on the lifecycle of the project for which it was written to support, and is not actively maintained outside of that scope. Use at your own risk.** 10 | 11 | ## Table of contents 12 | 13 | - [Overview](#overview) 14 | - [Host Requirements](#reqts) 15 | - [Configuration](#config) 16 | - [Deploy](#deploy) 17 | - [GitHub OAuth integration](#github) 18 | - [Teardown](#teardown) 19 | - [References](#references) 20 | - [Notes](#notes) 21 | 22 | ## Overview 23 | 24 | Jenkins offers a simple way to set up a continuous integration or continuous delivery environment for almost any combination of languages and source code repositories using pipelines, as well as automating other routine development tasks. While Jenkins doesn’t eliminate the need to create scripts for individual steps, it does give you a faster and more robust way to integrate your entire chain of build, test, and deployment tools than you can easily build yourself. 25 | 26 | 27 | - This work is based on the **Official Jenkins LTS Docker Image** [jenkins/jenkins:lts-jdk11](https://github.com/jenkinsci/docker) 28 | - Installs some prerequisites, configures the official Docker apt repositories and installs the latest **Docker CE** binaries 29 | - Mount the host machine's **Docker socket** in the container (This will allow your container to use the host machine's Docker daemon to run containers and build images). 30 | 31 | ### Host requirements 32 | 33 | Both Docker and Docker Compose are required on the host to run this code 34 | 35 | - Install Docker Engine: [https://docs.docker.com/engine/install/](https://docs.docker.com/engine/install/) 36 | - Install Docker Compose: [https://docs.docker.com/compose/install/](https://docs.docker.com/compose/install/) 37 | 38 | ## Configuration 39 | 40 | Copy the `env.template` file as `.env` and populate according to your environment 41 | 42 | ```ini 43 | # docker-compose environment file 44 | # 45 | # When you set the same environment variable in multiple files, 46 | # here’s the priority used by Compose to choose which value to use: 47 | # 48 | # 1. Compose file 49 | # 2. Shell environment variables 50 | # 3. Environment file 51 | # 4. Dockerfile 52 | # 5. Variable is not defined 53 | 54 | # Jenkins Settings 55 | export JENKINS_LOCAL_HOME=./jenkins_home 56 | export JENKINS_UID=1000 57 | export JENKINS_GID=1000 58 | export HOST_DOCKER_SOCK=/var/run/docker.sock 59 | 60 | # Nginx Settings 61 | export NGINX_CONF=./nginx/default.conf 62 | export NGINX_SSL_CERTS=./ssl 63 | export NGINX_LOGS=./logs/nginx 64 | 65 | # User Settings 66 | # TBD 67 | ``` 68 | 69 | Modify `nginx/default.conf` and replace `$host:8443` with your server domain name and port throughout the file 70 | 71 | ```conf 72 | upstream jenkins { 73 | keepalive 32; # keepalive connections 74 | server cicd-jenkins:8080; # jenkins container ip and port 75 | } 76 | 77 | # Required for Jenkins websocket agents 78 | map $http_upgrade $connection_upgrade { 79 | default upgrade; 80 | '' close; 81 | } 82 | 83 | server { 84 | listen 80; # Listen on port 80 for IPv4 requests 85 | server_name $host; 86 | return 301 https://$host:8443$request_uri; # replace '8443' with your https port 87 | } 88 | 89 | server { 90 | listen 443 ssl; # Listen on port 443 for IPv4 requests 91 | server_name $host:8443; # replace '$host:8443' with your server domain name and port 92 | 93 | # SSL certificate - replace as required with your own trusted certificate 94 | ssl_certificate /etc/ssl/fullchain.pem; 95 | ssl_certificate_key /etc/ssl/privkey.pem; 96 | 97 | # logging 98 | access_log /var/log/nginx/jenkins.access.log; 99 | error_log /var/log/nginx/jenkins.error.log; 100 | 101 | # this is the jenkins web root directory 102 | # (mentioned in the /etc/default/jenkins file) 103 | root /var/jenkins_home/war/; 104 | 105 | # pass through headers from Jenkins that Nginx considers invalid 106 | ignore_invalid_headers off; 107 | 108 | location ~ "^/static/[0-9a-fA-F]{8}\/(.*)$" { 109 | # rewrite all static files into requests to the root 110 | # E.g /static/12345678/css/something.css will become /css/something.css 111 | rewrite "^/static/[0-9a-fA-F]{8}\/(.*)" /$1 last; 112 | } 113 | 114 | location /userContent { 115 | # have nginx handle all the static requests to userContent folder 116 | # note : This is the $JENKINS_HOME dir 117 | root /var/jenkins_home/; 118 | if (!-f $request_filename) { 119 | # this file does not exist, might be a directory or a /**view** url 120 | rewrite (.*) /$1 last; 121 | break; 122 | } 123 | sendfile on; 124 | } 125 | 126 | location / { 127 | sendfile off; 128 | proxy_pass http://jenkins; 129 | proxy_redirect default; 130 | proxy_http_version 1.1; 131 | 132 | # Required for Jenkins websocket agents 133 | proxy_set_header Connection $connection_upgrade; 134 | proxy_set_header Upgrade $http_upgrade; 135 | 136 | proxy_set_header Host $host; 137 | proxy_set_header X-Real-IP $remote_addr; 138 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 139 | proxy_set_header X-Forwarded-Proto $scheme; 140 | proxy_set_header X-Forwarded-Host $host; 141 | proxy_set_header X-Forwarded-Port 8443; # replace '8443' with your https port 142 | proxy_max_temp_file_size 0; 143 | 144 | #this is the maximum upload size 145 | client_max_body_size 10m; 146 | client_body_buffer_size 128k; 147 | 148 | proxy_connect_timeout 90; 149 | proxy_send_timeout 90; 150 | proxy_read_timeout 90; 151 | proxy_buffering off; 152 | proxy_request_buffering off; # Required for HTTP CLI commands 153 | proxy_set_header Connection ""; # Clear for keepalive 154 | } 155 | 156 | } 157 | ``` 158 | 159 | ## Deploy 160 | 161 | Once configured the containers can be brought up using Docker Compose 162 | 163 | ```console 164 | source .env 165 | docker-compose pull 166 | docker-compose build 167 | docker-compose up -d 168 | ``` 169 | 170 | After a few moments the containers should be observed as running 171 | 172 | ```console 173 | $ docker-compose ps 174 | NAME COMMAND SERVICE STATUS PORTS 175 | cicd-jenkins "/sbin/tini -- /dock…" jenkins running 0.0.0.0:50000->50000/tcp 176 | cicd-nginx "/docker-entrypoint.…" nginx running 0.0.0.0:8080->80/tcp, 0.0.0.0:8443->443/tcp 177 | ``` 178 | 179 | The Jenkins application can be reached at the designated host and port (e.g. [https://127.0.0.1:8443]()). 180 | 181 | - **NOTE**: you will likely have to acknowledge the security risk if using the included self-signed certificate. 182 | 183 | ![](./imgs/jenkins-unlock.png) 184 | 185 | Copy the `Administrator password` from the docker log output of the jenkins container. 186 | 187 | ```console 188 | $ docker-compose logs jenkins 189 | cicd-jenkins | usermod: no changes 190 | ... 191 | cicd-jenkins | 2022-01-26 16:37:12.863+0000 [id=34] INFO jenkins.install.SetupWizard#init: 192 | cicd-jenkins | 193 | cicd-jenkins | ************************************************************* 194 | cicd-jenkins | ************************************************************* 195 | cicd-jenkins | ************************************************************* 196 | cicd-jenkins | 197 | cicd-jenkins | Jenkins initial setup is required. An admin user has been created and a password generated. 198 | cicd-jenkins | Please use the following password to proceed to installation: 199 | cicd-jenkins | 200 | cicd-jenkins | 1cd0eea03abc4ff1b184547625dd48f2 201 | cicd-jenkins | 202 | cicd-jenkins | This may also be found at: /var/jenkins_home/secrets/initialAdminPassword 203 | cicd-jenkins | 204 | cicd-jenkins | ************************************************************* 205 | cicd-jenkins | ************************************************************* 206 | cicd-jenkins | ************************************************************* 207 | cicd-jenkins | 208 | cicd-jenkins | 2022-01-26 16:37:24.156+0000 [id=34] INFO jenkins.InitReactorRunner$1#onAttained: Completed initialization 209 | ``` 210 | 211 | Install the plugins you are interested in 212 | 213 | ![](./imgs/jenkins-plugins-select.png) 214 | 215 | Setup an Administrative user 216 | 217 | ![](./imgs/jenkins-admin-user.png) 218 | 219 | Confirm the URL and start using Jenkins 220 | 221 | ![](./imgs/jenkins-url.png) 222 | ![](./imgs/jenkins-is-ready.png) 223 | ![](./imgs/jenkins-landing-page.png) 224 | 225 | ## GitHub OAuth integration 226 | 227 | **GOAL**: Use GitHub to authenticate user access into Jenkins and authorize actions within it 228 | 229 | - Authorization is delegated using **Matrix-based security** by `organization` or `organization*repo` represented as **Group** membership at the user level 230 | - Do **NOT** log off the initial Administrator account prior to establishing a new Administrative User or Group that is accessible to an authorized GitHub user 231 | - Once the **GitHub Authentication Plugin** is activated you will lose the standard login option of Username/Password 232 | - After installing, the `` class should have been updated in your `/var/lib/jenkins/config.xml` file. The value of `` should agree with what you pasted into the admin UI. If it doesn't or you still can't log in, reset to `` and restart Jenkins from the command-line. 233 | 234 | ### Install the GitHub OAuth Plugin 235 | 236 | - From Plugin Manager search for [GitHub Authentication](https://plugins.jenkins.io/github-oauth/) 237 | - Download and install after restart 238 | 239 | ### Setup 240 | 241 | Before configuring the plugin you must create a GitHub application registration. 242 | 243 | 1. Visit [https://github.com/settings/applications/new](https://github.com/settings/applications/new) to create a GitHub application registration. 244 | 2. The values for application name, homepage URL, or application description don't matter. They can be customized however desired. 245 | 3. However, the authorization callback URL takes a specific value. It must be `https://jenkins.example.com/securityRealm/finishLogin` where jenkins.example.com is the location of the Jenkins server. 246 | The important part of the callback URL is `/securityRealm/finishLogin` 247 | 4. Finish by clicking Register application. 248 | 249 | The `Client ID` and the `Client Secret` will be used to configure the Jenkins Security Realm. Keep the page open to the application registration so this information can be copied to your Jenkins configuration. 250 | 251 | ### Security Realm in Global Security 252 | 253 | The security realm in Jenkins controls authentication (i.e. you are who you say you are). The GitHub Authentication Plugin provides a security realm to authenticate Jenkins users via GitHub OAuth. 254 | 255 | 1. In the Global Security configuration choose the Security Realm to be **GitHub Authentication Plugin**. 256 | 2. The settings to configure are: GitHub Web URI, GitHub API URI, Client ID, Client Secret, and OAuth Scope(s). 257 | 3. If you're using GitHub Enterprise then the API URI is [https://ghe.example.com/api/v3](). 258 | The GitHub Enterprise API URI ends with `/api/v3`. 259 | 4. The recommended minimum GitHub OAuth scopes are `read:org,user:email`. 260 | The recommended scopes are designed for using both authentication and authorization functions in the plugin. If only authentication is being used then the scope can be further limited to `(no scope)` or `user:email`. 261 | 262 | In the plugin configuration pages each field has a little (**?**) next to it. Click on it for help about the setting. 263 | 264 | ![](./imgs/jenkins-github-oauth.png) 265 | 266 | ### Authorization in Global Security. 267 | 268 | The authorization configuration in Jenkins controls what your users can do (i.e. read jobs, execute builds, administer permissions, etc.). The GitHub OAuth Plugin supports multiple ways of configuring authorization. 269 | 270 | It is highly recommended that you configure the security realm and log in via GitHub OAuth before configuring authorization. This way Jenkins can look up and verify users and groups if configuring matrix-based authorization. 271 | 272 | ### Matrix-based Authorization strategy 273 | 274 | Control user authorization using **Matrix-based security** or **Project-based Matrix Authorization Strategy**. Project-based Matrix Authorization Strategy allows one to configure authorization globally per project and, when using Project-based Matrix Authorization Strategy with the CloudBees folder plugin, per folder. 275 | 276 | There are a few built-in authorizations to consider. 277 | 278 | - `anonymous` - is anyone who has not logged in. Recommended permissions are just `Job/Discover` and `Job/ViewStatus`. 279 | - `authenticated` - is anyone who has logged in. You can configure permissions for anybody who has logged into Jenkins. Recommended permissions are `Overall/Read` and `View/Read`. 280 | `anonymous` and `authenticated` usernames are case sensitive and must be lower case. This is a consideration when configuring authorizations via Groovy. Keep in mind that anonymous shows up as Anonymous in the Jenkins UI. 281 | 282 | You can configure authorization based on GitHub users, organizations, or teams. 283 | 284 | - `username` - give permissions to a specific GitHub username. 285 | - `organization` - give permissions to every user that belongs to a specific GitHub organization. 286 | - `organization*team` - give permissions to a specific GitHub team of a GitHub organization. Notice that organization and team are separated by an asterisk (*). 287 | 288 | ![](./imgs/jenkins-authorization-matrix.png) 289 | 290 | Related to the screenshot above: 291 | 292 | - User: `Michael J. Stealey` is an Administrative user that authenticated using the GitHub OAuth Plugin 293 | - User: `Jenkins Admin` is the original admin account but is no longer reachable using the GitHub OAuth Plugin 294 | - Group: `RENCI-NRIG` is a GitHub Organization reflected as a Group for any users who belong to that organization and is being used to set **Job** based authorizations within Jenkins 295 | 296 | **Reference**: 297 | 298 | - Documentation: [https://plugins.jenkins.io/github-oauth/](https://plugins.jenkins.io/github-oauth/) 299 | - GitHub: [https://github.com/jenkinsci/github-oauth-plugin](https://github.com/jenkinsci/github-oauth-plugin) 300 | 301 | ## Teardown 302 | 303 | For a complete teardown all containers must be stopped and removed along with the volumes and network that were created for the application containers 304 | 305 | Commands 306 | 307 | ```console 308 | docker-compose stop 309 | docker-compose rm -fv 310 | docker-network rm cicd-jenkins 311 | # removal calls may require sudo rights depending on file permissions 312 | rm -rf ./jenkins_home 313 | rm -rf ./logs 314 | ``` 315 | 316 | Expected output 317 | 318 | ```console 319 | $ docker-compose stop 320 | [+] Running 2/2 321 | ⠿ Container cicd-nginx Stopped 0.9s 322 | ⠿ Container cicd-jenkins Stopped 1.0s 323 | $ docker-compose rm -fv 324 | Going to remove cicd-nginx, cicd-jenkins 325 | [+] Running 2/0 326 | ⠿ Container cicd-jenkins Removed 0.0s 327 | ⠿ Container cicd-nginx Removed 0.0s 328 | $ docker network rm cicd-jenkins 329 | cicd-jenkins 330 | $ rm -rf ./jenkins_home 331 | $ rm -rf ./logs 332 | ``` 333 | 334 | ## References 335 | 336 | - Jenkins Docker images: [https://github.com/jenkinsci/docker](https://github.com/jenkinsci/docker) 337 | - Jenkins Documentation: [https://www.jenkins.io/doc/](https://www.jenkins.io/doc/) 338 | - Docker Documentation: [https://docs.docker.com](https://docs.docker.com) 339 | 340 | --- 341 | 342 | ## Notes 343 | 344 | General information regarding standard Docker deployment of Jenkins for reference purposes 345 | 346 | ### Host volume mounts 347 | 348 | The provided configuration creates two host volume mounts that may require some permissions adjustments to work correctly. 349 | 350 | 1. `${JENKINS_LOCAL_HOME}:/var/jenkins_home` - Maps the `/var/jenkins_home` directory inside the `jenkins` container to the Docker volume identified by `${JENKINS_LOCAL_HOME}`. Other Docker containers controlled by this Docker container’s Docker daemon can mount data from Jenkins (e.g. the Nginx container). 351 | 352 | Generally speaking it is suggested to set the jenkins user UID and GID values to that of the user running the application. 353 | 354 | ```console 355 | $ id -u 356 | 1011 357 | $ id -g 358 | 1011 359 | ``` 360 | 361 | Then in the `.env` file set 362 | 363 | ```ini 364 | ... 365 | export JENKINS_UID=1011 366 | export JENKINS_GID=1011 367 | ... 368 | ``` 369 | 370 | 2. `${NGINX_LOGS}:/var/log/nginx` - Maps the `/var/log/nginx` directory inside the `nginx` container to the Docker volume identified by `${NGINX_LOGS}` 371 | 372 | There is no easy to change user UID/GID flag in Nginx, you either need to rebuild the Docker image with a custom UID/GID set, or just accept the log output as it is and use a privileged account to modify the files if needed. 373 | 374 | ### Deploy with /jenkins prefix 375 | 376 | At times it is desirable to run Jenkins at a non-root URL such as [https://127.0.0.1:8443/jenkins]() 377 | 378 | This can be achieved by adjusting a few parameters 379 | 380 | 1. Modify the `docker-compose.yml` file to include in `JENKINS_OPTS` 381 | 382 | ```yaml 383 | ... 384 | jenkins: 385 | # default ports 8080, 50000 - expose mapping as needed to host 386 | build: 387 | context: ./jenkins 388 | dockerfile: Dockerfile 389 | container_name: cicd-jenkins 390 | restart: unless-stopped 391 | networks: 392 | - jenkins 393 | ports: 394 | - "50000:50000" 395 | environment: 396 | # Uncomment JENKINS_OPTS to add prefix: e.g. https://127.0.0.1:8443/jenkins 397 | - JENKINS_OPTS="--prefix=/jenkins" # <-- Uncomment this line 398 | - JENKINS_UID=${JENKINS_UID} 399 | - JENKINS_GID=${JENKINS_GID} 400 | volumes: 401 | - ${JENKINS_LOCAL_HOME}:/var/jenkins_home 402 | - ${HOST_DOCKER_SOCK}:/var/run/docker.sock 403 | ``` 404 | 405 | 2. Modify `.env` to use the `nginx/prefix_jenkins.conf` file instead of `nginx/default.conf` 406 | 407 | ```ini 408 | ... 409 | # Nginx Settings 410 | export NGINX_CONF=./nginx/prefix_jenkins.conf 411 | export NGINX_SSL_CERTS=./ssl 412 | export NGINX_LOGS=./logs/nginx 413 | ``` 414 | 415 | 3. Follow the **Deploy** instructions from above 416 | 417 | ### Let's Encrypt SSL Certificate 418 | 419 | Use: [https://github.com/RENCI-NRIG/ez-letsencrypt](https://github.com/RENCI-NRIG/ez-letsencrypt) - A shell script to obtain and renew [Let's Encrypt](https://letsencrypt.org/) certificates using Certbot's `--webroot` method of [certificate issuance](https://certbot.eff.org/docs/using.html#webroot). 420 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | services: 3 | 4 | jenkins: 5 | # default ports 8080, 50000 - expose mapping as needed to host 6 | build: 7 | context: ./jenkins 8 | dockerfile: Dockerfile 9 | container_name: cicd-jenkins 10 | restart: unless-stopped 11 | networks: 12 | - jenkins 13 | ports: 14 | - "50000:50000" 15 | environment: 16 | # Uncomment JENKINS_OPTS to add prefix: e.g. https://127.0.0.1:8443/jenkins 17 | #- JENKINS_OPTS="--prefix=/jenkins" 18 | - JENKINS_UID=${JENKINS_UID} 19 | - JENKINS_GID=${JENKINS_GID} 20 | volumes: 21 | - ${JENKINS_LOCAL_HOME}:/var/jenkins_home 22 | - ${HOST_DOCKER_SOCK}:/var/run/docker.sock 23 | 24 | nginx: 25 | # default ports 80, 443 - expose mapping as needed to host 26 | image: nginx:1 27 | container_name: cicd-nginx 28 | restart: unless-stopped 29 | networks: 30 | - jenkins 31 | ports: 32 | - "8080:80" # http 33 | - "8443:443" # https 34 | volumes: 35 | - ${JENKINS_LOCAL_HOME}:/var/jenkins_home 36 | - ${NGINX_CONF}:/etc/nginx/conf.d/default.conf 37 | - ${NGINX_SSL_CERTS}:/etc/ssl 38 | - ${NGINX_LOGS}:/var/log/nginx 39 | 40 | networks: 41 | jenkins: 42 | name: cicd-jenkins 43 | driver: bridge -------------------------------------------------------------------------------- /env.template: -------------------------------------------------------------------------------- 1 | # docker-compose environment file 2 | # 3 | # When you set the same environment variable in multiple files, 4 | # here’s the priority used by Compose to choose which value to use: 5 | # 6 | # 1. Compose file 7 | # 2. Shell environment variables 8 | # 3. Environment file 9 | # 4. Dockerfile 10 | # 5. Variable is not defined 11 | 12 | # Jenkins Settings 13 | export JENKINS_LOCAL_HOME=./jenkins_home 14 | export JENKINS_UID=1000 15 | export JENKINS_GID=1000 16 | export HOST_DOCKER_SOCK=/var/run/docker.sock 17 | 18 | # Nginx Settings 19 | export NGINX_CONF=./nginx/default.conf 20 | export NGINX_SSL_CERTS=./ssl 21 | export NGINX_LOGS=./logs/nginx 22 | 23 | # User Settings 24 | # TBD -------------------------------------------------------------------------------- /imgs/jenkins-admin-user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjstealey/jenkins-nginx-docker/8e5027c1c9212537abce15138824f55f17f07cd3/imgs/jenkins-admin-user.png -------------------------------------------------------------------------------- /imgs/jenkins-authorization-matrix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjstealey/jenkins-nginx-docker/8e5027c1c9212537abce15138824f55f17f07cd3/imgs/jenkins-authorization-matrix.png -------------------------------------------------------------------------------- /imgs/jenkins-github-oauth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjstealey/jenkins-nginx-docker/8e5027c1c9212537abce15138824f55f17f07cd3/imgs/jenkins-github-oauth.png -------------------------------------------------------------------------------- /imgs/jenkins-is-ready.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjstealey/jenkins-nginx-docker/8e5027c1c9212537abce15138824f55f17f07cd3/imgs/jenkins-is-ready.png -------------------------------------------------------------------------------- /imgs/jenkins-landing-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjstealey/jenkins-nginx-docker/8e5027c1c9212537abce15138824f55f17f07cd3/imgs/jenkins-landing-page.png -------------------------------------------------------------------------------- /imgs/jenkins-plugins-select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjstealey/jenkins-nginx-docker/8e5027c1c9212537abce15138824f55f17f07cd3/imgs/jenkins-plugins-select.png -------------------------------------------------------------------------------- /imgs/jenkins-unlock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjstealey/jenkins-nginx-docker/8e5027c1c9212537abce15138824f55f17f07cd3/imgs/jenkins-unlock.png -------------------------------------------------------------------------------- /imgs/jenkins-url.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjstealey/jenkins-nginx-docker/8e5027c1c9212537abce15138824f55f17f07cd3/imgs/jenkins-url.png -------------------------------------------------------------------------------- /jenkins/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM jenkins/jenkins:lts-jdk11 2 | MAINTAINER Michael J. Stealey 3 | 4 | # add ability to run docker from within jenkins (docker in docker) 5 | USER root 6 | RUN apt-get update && apt-get install -y lsb-release sudo 7 | RUN curl -fsSLo /usr/share/keyrings/docker-archive-keyring.asc \ 8 | https://download.docker.com/linux/debian/gpg 9 | RUN echo "deb [arch=$(dpkg --print-architecture) \ 10 | signed-by=/usr/share/keyrings/docker-archive-keyring.asc] \ 11 | https://download.docker.com/linux/debian \ 12 | $(lsb_release -cs) stable" > /etc/apt/sources.list.d/docker.list 13 | RUN apt-get update && apt-get install -y docker-ce-cli \ 14 | && apt-get clean \ 15 | && rm -rf /var/lib/apt/lists/* 16 | 17 | USER jenkins 18 | 19 | # set default user attributes 20 | ENV JENKINS_UID=1000 21 | ENV JENKINS_GID=1000 22 | 23 | # add entrypoint script 24 | COPY docker-entrypoint.sh /docker-entrypoint.sh 25 | 26 | # normally user would be set to jenkins, but this is handled by the docker-entrypoint script on startup 27 | #USER jenkins 28 | USER root 29 | 30 | # bypass normal entrypoint and use custom one 31 | #ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/jenkins.sh"] 32 | ENTRYPOINT ["/sbin/tini", "--", "/docker-entrypoint.sh"] 33 | -------------------------------------------------------------------------------- /jenkins/docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | # jenkins user requires valid UID:GID permissions at the host level to persist data 5 | # 6 | # set Jenkins UID and GID values (as root) 7 | usermod -u ${JENKINS_UID} jenkins 8 | groupmod -g ${JENKINS_GID} jenkins 9 | # update ownership of directories (as root) 10 | { 11 | chown -R jenkins:jenkins /var/jenkins_home 12 | chown -R jenkins:jenkins /usr/share/jenkins/ref 13 | } || 14 | { 15 | echo "[ERROR] Failed 'chown -R jenkins:jenkins ...' command" 16 | } 17 | 18 | # allow jenkins to run sudo docker (as root) 19 | echo "jenkins ALL=(root) NOPASSWD: /usr/bin/docker" > /etc/sudoers.d/jenkins 20 | chmod 0440 /etc/sudoers.d/jenkins 21 | 22 | # run Jenkins (as jenkins) 23 | sed -i "s# exec java# exec $(which java)#g" /usr/local/bin/jenkins.sh 24 | su jenkins -c 'cd $HOME; export PATH=$PATH:$(which java); /usr/local/bin/jenkins.sh' 25 | -------------------------------------------------------------------------------- /nginx/default.conf: -------------------------------------------------------------------------------- 1 | upstream jenkins { 2 | keepalive 32; # keepalive connections 3 | server cicd-jenkins:8080; # jenkins container ip and port 4 | } 5 | 6 | # Required for Jenkins websocket agents 7 | map $http_upgrade $connection_upgrade { 8 | default upgrade; 9 | '' close; 10 | } 11 | 12 | server { 13 | listen 80; # Listen on port 80 for IPv4 requests 14 | server_name $host; 15 | return 301 https://$host:8443$request_uri; # replace '8443' with your https port 16 | } 17 | 18 | server { 19 | listen 443 ssl; # Listen on port 443 for IPv4 requests 20 | server_name $host:8443; # replace '$host:8443' with your server domain name and port 21 | 22 | # Enable support for TLS 1.2 and/or 1.3 23 | ssl_protocols TLSv1.2 TLSv1.3; 24 | 25 | # SSL certificate - replace as required with your own trusted certificate 26 | ssl_certificate /etc/ssl/fullchain.pem; 27 | ssl_certificate_key /etc/ssl/privkey.pem; 28 | 29 | # logging 30 | access_log /var/log/nginx/jenkins.access.log; 31 | error_log /var/log/nginx/jenkins.error.log; 32 | 33 | # this is the jenkins web root directory 34 | # (mentioned in the /etc/default/jenkins file) 35 | root /var/jenkins_home/war/; 36 | 37 | # pass through headers from Jenkins that Nginx considers invalid 38 | ignore_invalid_headers off; 39 | 40 | location ~ "^/static/[0-9a-fA-F]{8}\/(.*)$" { 41 | # rewrite all static files into requests to the root 42 | # E.g /static/12345678/css/something.css will become /css/something.css 43 | rewrite "^/static/[0-9a-fA-F]{8}\/(.*)" /$1 last; 44 | } 45 | 46 | location /userContent { 47 | # have nginx handle all the static requests to userContent folder 48 | # note : This is the $JENKINS_HOME dir 49 | root /var/jenkins_home/; 50 | if (!-f $request_filename) { 51 | # this file does not exist, might be a directory or a /**view** url 52 | rewrite (.*) /$1 last; 53 | break; 54 | } 55 | sendfile on; 56 | } 57 | 58 | location / { 59 | sendfile off; 60 | proxy_pass http://jenkins; 61 | proxy_redirect default; 62 | proxy_http_version 1.1; 63 | 64 | # Required for Jenkins websocket agents 65 | proxy_set_header Connection $connection_upgrade; 66 | proxy_set_header Upgrade $http_upgrade; 67 | 68 | proxy_set_header Host $host; 69 | proxy_set_header X-Real-IP $remote_addr; 70 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 71 | proxy_set_header X-Forwarded-Proto $scheme; 72 | proxy_set_header X-Forwarded-Host $host; 73 | proxy_set_header X-Forwarded-Port 8443; # replace '8443' with your https port 74 | proxy_max_temp_file_size 0; 75 | 76 | #this is the maximum upload size 77 | client_max_body_size 10m; 78 | client_body_buffer_size 128k; 79 | 80 | proxy_connect_timeout 90; 81 | proxy_send_timeout 90; 82 | proxy_read_timeout 90; 83 | proxy_buffering off; 84 | proxy_request_buffering off; # Required for HTTP CLI commands 85 | proxy_set_header Connection ""; # Clear for keepalive 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /nginx/prefix_jenkins.conf: -------------------------------------------------------------------------------- 1 | upstream jenkins { 2 | keepalive 32; # keepalive connections 3 | server cicd-jenkins:8080; # jenkins container ip and port 4 | } 5 | 6 | # Required for Jenkins websocket agents 7 | map $http_upgrade $connection_upgrade { 8 | default upgrade; 9 | '' close; 10 | } 11 | 12 | server { 13 | listen 80; # Listen on port 80 for IPv4 requests 14 | server_name $host; 15 | return 301 https://$host:8443$request_uri; # replace '8443' with your https port 16 | } 17 | 18 | server { 19 | listen 443 ssl; # Listen on port 443 for IPv4 requests 20 | server_name $host:8443; # replace '$host:8443' with your server domain name and port 21 | 22 | # SSL certificate - replace as required with your own trusted certificate 23 | ssl_certificate /etc/ssl/fullchain.pem; 24 | ssl_certificate_key /etc/ssl/privkey.pem; 25 | 26 | # logging 27 | access_log /var/log/nginx/jenkins.access.log; 28 | error_log /var/log/nginx/jenkins.error.log; 29 | 30 | # this is the jenkins web root directory 31 | # (mentioned in the /etc/default/jenkins file) 32 | root /var/jenkins_home/war/; 33 | 34 | # pass through headers from Jenkins that Nginx considers invalid 35 | ignore_invalid_headers off; 36 | 37 | location ~ "^/static/[0-9a-fA-F]{8}\/(.*)$" { 38 | # rewrite all static files into requests to the root 39 | # E.g /static/12345678/css/something.css will become /css/something.css 40 | rewrite "^/static/[0-9a-fA-F]{8}\/(.*)" /$1 last; 41 | } 42 | 43 | location /jenkins/userContent { 44 | # have nginx handle all the static requests to userContent folder 45 | # note : This is the $JENKINS_HOME dir 46 | root /var/jenkins_home/; 47 | if (!-f $request_filename) { 48 | # this file does not exist, might be a directory or a /**view** url 49 | rewrite (.*) /$1 last; 50 | break; 51 | } 52 | sendfile on; 53 | } 54 | 55 | location /jenkins { 56 | sendfile off; 57 | proxy_pass http://jenkins; 58 | proxy_redirect default; 59 | proxy_http_version 1.1; 60 | 61 | # Required for Jenkins websocket agents 62 | proxy_set_header Connection $connection_upgrade; 63 | proxy_set_header Upgrade $http_upgrade; 64 | 65 | proxy_set_header Host $host; 66 | proxy_set_header X-Real-IP $remote_addr; 67 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 68 | proxy_set_header X-Forwarded-Proto $scheme; 69 | proxy_set_header X-Forwarded-Host $host; 70 | proxy_set_header X-Forwarded-Port 8443; # replace '8443' with your https port 71 | proxy_max_temp_file_size 0; 72 | 73 | #this is the maximum upload size 74 | client_max_body_size 10m; 75 | client_body_buffer_size 128k; 76 | 77 | proxy_connect_timeout 90; 78 | proxy_send_timeout 90; 79 | proxy_read_timeout 90; 80 | proxy_buffering off; 81 | proxy_request_buffering off; # Required for HTTP CLI commands 82 | proxy_set_header Connection ""; # Clear for keepalive 83 | } 84 | 85 | location / { 86 | rewrite ^/ $scheme://$host:8443/jenkins permanent; # replace '8443' with your https port 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /ssl/README.md: -------------------------------------------------------------------------------- 1 | # SSL - certificates for development 2 | 3 | SSL development certificates are included in this repository for demonstration purposes and should be replaced with genuine certificates in production. 4 | 5 | - `privkey.pem` : the private key for your certificate. 6 | - `fullchain.pem`: the certificate file used in most server software (copy of chain.pem for development purposes). 7 | - `chain.pem` : used for OCSP stapling in Nginx >=1.3.7. 8 | 9 | These certificates are self signed, and as such not reckognized by any CA. Do not use this for anything beyond local development (Never use in production) 10 | 11 | ### Generate `privkey.pem`, `fullchain.pem`, `chain.pem` 12 | 13 | Certificate generation based on Let's Encrypt [certificates for localhost](https://letsencrypt.org/docs/certificates-for-localhost/). 14 | 15 | ``` 16 | openssl req -x509 -outform pem -out chain.pem -keyout privkey.pem \ 17 | -newkey rsa:4096 -nodes -sha256 -days 3650 \ 18 | -subj '/CN=localhost' -extensions EXT -config <( \ 19 | printf "[dn]\nCN=localhost\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:localhost\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth") 20 | cat chain.pem > fullchain.pem 21 | ``` 22 | 23 | ### Reference 24 | 25 | Nginx configuration reference: [https://www.nginx.com/blog/using-free-ssltls-certificates-from-lets-encrypt-with-nginx/](https://www.nginx.com/blog/using-free-ssltls-certificates-from-lets-encrypt-with-nginx/) 26 | -------------------------------------------------------------------------------- /ssl/chain.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIE5TCCAs2gAwIBAgIJAKDNGmHgaI/NMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV 3 | BAMMCWxvY2FsaG9zdDAeFw0yMjAxMjExNDAwNThaFw0zMjAxMTkxNDAwNThaMBQx 4 | EjAQBgNVBAMMCWxvY2FsaG9zdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC 5 | ggIBAMF7unNwZltgWhdtCoPJXYJ164UaeEme0lWAR+pT27HD2vkl6vQXV4Rqwpef 6 | YlpXlpygxZ3ShL8767cDmcMk/AVqmbaPMEUWrUE5ufPatJU/90m4HDgwoy448Q9l 7 | ZsqEe3L5Z2kkmEIByGVY/cnmMmBHPnRggEDer1QeKt5mefQA5B/bikeYPGuYSiWC 8 | B+QRJ8QY5YFQUZc2ygZh64ozXgcuHcaphsxfXbP7WvJNx+cx1lgBOMNDoK4376Bl 9 | yosFdnr7UOvI+0pCF73hpC7oc3M3RSi/jwoD0twnULwyP5YwA1MmwBtuIVA0BDqC 10 | AFPdE//L3fQERxbuTSShIXICDXPpDQKwxyeY/5e+1bmrVudBh98+Ddtcy6nbDnER 11 | LVFMSEZrYSOh4eebNRdgZC8A75+F87IhX++Nh9ora2BzT98zggYJNqSnfA0nUCXo 12 | Mx1+Z+heDYlUE/92nT/gRW1DjUMPn6Ge+p5DzCgvVWIvfj+x1qOOY1cV+I8REvJK 13 | roK9+L75fCLPdni02dyetBxa6O6hoQnvt+WnyPLOaef0TMxskQtiGQoBi19Tr/hL 14 | XjkIF8lVAewHG4lntosTnLSLy5SA+de9ZFwl7gi7IT7/fBhiEoFn5vCxB5INqK6j 15 | Vs2KayJOdJQRDxB1ty4SyGRohLwBlLV65KEOL5Mu1eorSZTzAgMBAAGjOjA4MBQG 16 | A1UdEQQNMAuCCWxvY2FsaG9zdDALBgNVHQ8EBAMCB4AwEwYDVR0lBAwwCgYIKwYB 17 | BQUHAwEwDQYJKoZIhvcNAQELBQADggIBAItWoIaimCtqJIH+KbkX6XIth3YBKSQS 18 | mnKszI0C0ScniWIdvFsKYOt9lmSmILN6M4YfEV9lEBBd64RbxvDkLVMkkpYSCuJl 19 | QlohBbGPjYHumWnyOhrB1zLYBs73ljbqetLSZjeGzbkz6xwUVHnkEnhvPCAzVnfY 20 | W2B4+/t2ng9qCO6i8JiAV64I849ZrQeCjp9giF0WEx9LSzzcc/pMly1frVP7Vx70 21 | xygXcyGfrd/t37mLVD3sXLDQxXFZPtII7WMFnj7F5IAGUQ7f/sgunjKTgkpJt53K 22 | T8rsBnZhEyxgaCKbMp659ft3uMXE58NKrCg8rZCa7Ld0TDiD6vZg4+RpzAl37sWr 23 | WRIvcK6zcSSIHtPnIvHHeZj2YgwRIOT8e649OoRVwo+4LgXA5GhkbUMSX/fkf8NQ 24 | 45S5mzlep0c/bStx6pLDz29RbqIlzK5Jo4Ugi2DkNB7snE8tEg5N+09vpwo/8tsV 25 | YP7CA7BlYaU6exAQiRx8gA97eGFEX4n8w8yRfuo5wcSAWDvKHWbwNcHUBNMMP3Fe 26 | oHnEEcFTNHDPz+P4swywdrOFcOF0tyhjQl/BEjf3/FrwHpiEX8YyXKJX26kg0M8q 27 | wU01nGDEBVnCTKkN6yjIH4+kGme0MQVrNItWWT/nOMS4iGtJYfvXBNVXd8uZujKw 28 | U+5MFPXQUvqR 29 | -----END CERTIFICATE----- 30 | -------------------------------------------------------------------------------- /ssl/fullchain.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIE5TCCAs2gAwIBAgIJAKDNGmHgaI/NMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV 3 | BAMMCWxvY2FsaG9zdDAeFw0yMjAxMjExNDAwNThaFw0zMjAxMTkxNDAwNThaMBQx 4 | EjAQBgNVBAMMCWxvY2FsaG9zdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC 5 | ggIBAMF7unNwZltgWhdtCoPJXYJ164UaeEme0lWAR+pT27HD2vkl6vQXV4Rqwpef 6 | YlpXlpygxZ3ShL8767cDmcMk/AVqmbaPMEUWrUE5ufPatJU/90m4HDgwoy448Q9l 7 | ZsqEe3L5Z2kkmEIByGVY/cnmMmBHPnRggEDer1QeKt5mefQA5B/bikeYPGuYSiWC 8 | B+QRJ8QY5YFQUZc2ygZh64ozXgcuHcaphsxfXbP7WvJNx+cx1lgBOMNDoK4376Bl 9 | yosFdnr7UOvI+0pCF73hpC7oc3M3RSi/jwoD0twnULwyP5YwA1MmwBtuIVA0BDqC 10 | AFPdE//L3fQERxbuTSShIXICDXPpDQKwxyeY/5e+1bmrVudBh98+Ddtcy6nbDnER 11 | LVFMSEZrYSOh4eebNRdgZC8A75+F87IhX++Nh9ora2BzT98zggYJNqSnfA0nUCXo 12 | Mx1+Z+heDYlUE/92nT/gRW1DjUMPn6Ge+p5DzCgvVWIvfj+x1qOOY1cV+I8REvJK 13 | roK9+L75fCLPdni02dyetBxa6O6hoQnvt+WnyPLOaef0TMxskQtiGQoBi19Tr/hL 14 | XjkIF8lVAewHG4lntosTnLSLy5SA+de9ZFwl7gi7IT7/fBhiEoFn5vCxB5INqK6j 15 | Vs2KayJOdJQRDxB1ty4SyGRohLwBlLV65KEOL5Mu1eorSZTzAgMBAAGjOjA4MBQG 16 | A1UdEQQNMAuCCWxvY2FsaG9zdDALBgNVHQ8EBAMCB4AwEwYDVR0lBAwwCgYIKwYB 17 | BQUHAwEwDQYJKoZIhvcNAQELBQADggIBAItWoIaimCtqJIH+KbkX6XIth3YBKSQS 18 | mnKszI0C0ScniWIdvFsKYOt9lmSmILN6M4YfEV9lEBBd64RbxvDkLVMkkpYSCuJl 19 | QlohBbGPjYHumWnyOhrB1zLYBs73ljbqetLSZjeGzbkz6xwUVHnkEnhvPCAzVnfY 20 | W2B4+/t2ng9qCO6i8JiAV64I849ZrQeCjp9giF0WEx9LSzzcc/pMly1frVP7Vx70 21 | xygXcyGfrd/t37mLVD3sXLDQxXFZPtII7WMFnj7F5IAGUQ7f/sgunjKTgkpJt53K 22 | T8rsBnZhEyxgaCKbMp659ft3uMXE58NKrCg8rZCa7Ld0TDiD6vZg4+RpzAl37sWr 23 | WRIvcK6zcSSIHtPnIvHHeZj2YgwRIOT8e649OoRVwo+4LgXA5GhkbUMSX/fkf8NQ 24 | 45S5mzlep0c/bStx6pLDz29RbqIlzK5Jo4Ugi2DkNB7snE8tEg5N+09vpwo/8tsV 25 | YP7CA7BlYaU6exAQiRx8gA97eGFEX4n8w8yRfuo5wcSAWDvKHWbwNcHUBNMMP3Fe 26 | oHnEEcFTNHDPz+P4swywdrOFcOF0tyhjQl/BEjf3/FrwHpiEX8YyXKJX26kg0M8q 27 | wU01nGDEBVnCTKkN6yjIH4+kGme0MQVrNItWWT/nOMS4iGtJYfvXBNVXd8uZujKw 28 | U+5MFPXQUvqR 29 | -----END CERTIFICATE----- 30 | -------------------------------------------------------------------------------- /ssl/privkey.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | 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 51 | igsPvMAZsuT8bjgaZjA2B2xi2C01t0f0 52 | -----END PRIVATE KEY----- 53 | --------------------------------------------------------------------------------