1. Which metric directly measures the time from commit to a production deployment?
Lead time tells you how quickly code flows from commit to release. It is a key delivery speed signal used widely in DevOps reporting.
Get the Preplance app for a seamless learning experience. Practice offline, get daily streaks, and stay ahead with real-time interview updates.
Get it on
Google Play
4.9/5 Rating on Store
Amazon · CI/CD Pipelines
Practice CI/CD Pipelines questions specifically asked in Amazon interviews – ideal for online test preparation, technical rounds and final HR discussions.
Questions
97
Tagged for this company + subject
Company
Amazon
View company-wise questions
Subject
CI/CD Pipelines
Explore topic-wise practice
Go through each question and its explanation. Use this page for targeted revision just before your Amazon CI/CD Pipelines round.
Lead time tells you how quickly code flows from commit to release. It is a key delivery speed signal used widely in DevOps reporting.
For complete preparation, combine this company + subject page with full company-wise practice and subject-wise practice. You can also explore other companies and topics from the links below.
Code organization: use Declarative Pipeline for consistency and maintainability, store Jenkinsfile in repository root under version control, use Shared Libraries for common code across pipelines, organize stages logically (Build, Test, Deploy), extract complex logic to functions or Shared Libraries, keep Jenkinsfile readable with clear stage names and comments. Pipeline structure best practices: ```groovy @Library('shared-library') _ pipeline { agent none // Define per stage for flexibility options { timestamps() timeout(time: 1, unit: 'HOURS') buildDiscarder(logRotator(numToKeepStr: '30')) } environment { APP_NAME = 'myapp' REGISTRY = 'docker.io' } stages { stage('Build') { agent { docker 'maven:3.8-jdk-11' } steps { sh 'mvn clean package' } } stage('Test') { parallel { stage('Unit Tests') { agent any steps { sh 'mvn test' } } stage('Integration Tests') { agent any steps { sh 'mvn verify' } } } } stage('Deploy') { when { branch 'main' } agent any steps { script { deployToEnvironment('production') } } } } post { always { junit '**/target/test-results/*.xml' cleanWs() } success { slackSend color: 'good', message: 'Build succeeded' } failure { slackSend color: 'danger', message: 'Build failed' } } } ``` Security best practices: never hardcode secrets in Jenkinsfile, use credentials plugin with withCredentials, restrict credential access with folder-level permissions, scan credentials regularly for exposure, use least privilege (minimal permissions), enable CSRF protection, implement audit logging, use HTTPS for Jenkins, regularly update Jenkins and plugins, scan container images for vulnerabilities. Credentials usage: ```groovy withCredentials([usernamePassword( credentialsId: 'docker-hub', usernameVariable: 'DOCKER_USER', passwordVariable: 'DOCKER_PASS' )]) { sh 'docker login -u $DOCKER_USER -p $DOCKER_PASS' } ``` Testing pipelines: use replay feature for testing changes without committing, create test branch for pipeline development, use Multibranch Pipeline testing in feature branches, implement pipeline unit tests with JenkinsPipelineUnit, validate Jenkinsfile syntax with Jenkins API or CLI, test Shared Library functions independently. Error handling and resilience: set timeouts preventing hanging, implement retry for flaky operations, use catchError for non-critical steps, add meaningful error messages, use post conditions for cleanup, implement proper notifications, log important state for debugging. Performance optimization: use parallel stages for independent tasks, implement caching for dependencies, use appropriate agents (Docker, labeled), clean workspaces regularly, skip unnecessary stages with when, use shallow git clone, limit build history retention. Documentation: comment complex logic, document pipeline parameters, create README for shared libraries, maintain changelog for pipeline changes, document required agent labels and tools, provide examples for common use cases, document troubleshooting procedures. Maintenance: regularly update Jenkins and plugins, review and refactor pipelines periodically, monitor pipeline metrics (duration, success rate), remove unused pipelines, standardize pipeline patterns across organization, conduct pipeline code reviews, archive old jobs. Monitoring and observability: collect metrics (build duration, success rate, queue time), implement monitoring dashboards, set up alerts for failures, track agent utilization, monitor plugin health, log important pipeline events, use Blue Ocean for better visualization. Version control practices: commit Jenkinsfile with application code, use meaningful commit messages, protect Jenkinsfile with branch protection, review Jenkinsfile changes through pull requests, tag releases including Jenkinsfile version, use semantic versioning for Shared Libraries. Best practices checklist: Declarative over Scripted, version controlled Jenkinsfile, secrets in credentials, timeouts set, cleanup in post always, parallel independent stages, proper error handling, meaningful stage names, documented parameters, regular maintenance, monitored metrics. Understanding and implementing best practices creates maintainable, secure, efficient pipeline infrastructure.
The pipeline watches the repo for changes, so version control becomes the single source of truth. It gives history, reviews, and easy rollback, and it triggers automation the moment code changes.
Keep deployments versioned and immutable. If health checks fail or error rate spikes, promote the last good artifact back to production. For blue green or canary, flip traffic to the stable version while you investigate and fix forward in a new release.
Use a cache to reuse things like npm or Maven downloads and avoid repeated network work. Use artifacts to pass built outputs between stages. This split keeps runs fast and results reproducible.
cache: paths: - ~/.m2/repository - node_modules
Extract shared steps into templates or a shared library. Parameterize things like image name, test command, and deploy target. Each repo imports the template and overrides only what is unique. One change improves all pipelines.
uses: org/reusable-workflows/.github/workflows/build-test.yml@v1 with: service_name: payments
Protect the branch with required checks and approvals. If any check fails, the merge is blocked. This policy keeps main deployable at all times.
Require: build, unit, lint, security to pass before merge
Caching strategies dramatically reduce build time by reusing artifacts from previous builds: Dependency caching stores downloaded packages: ```yaml # GitLab CI variables: MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository" cache: key: ${CI_COMMIT_REF_SLUG} paths: - .m2/repository/ - node_modules/ - target/ ``` GitHub Actions: ```yaml - uses: actions/cache@v3 with: path: | ~/.m2/repository ~/.gradle/caches ~/.npm key: ${{ runner.os }}-build-${{ hashFiles('**/package-lock.json', '**/pom.xml') }} restore-keys: | ${{ runner.os }}-build- ``` Shared cache across builds and branches significantly reduces dependency download time. Parallelization runs independent tasks concurrently: ```yaml # GitLab parallel jobs test_unit: stage: test script: npm run test:unit test_integration: stage: test script: npm run test:integration test_e2e: stage: test script: npm run test:e2e ``` Three test suites run simultaneously if multiple runners available. Matrix parallelization: ```yaml # GitHub Actions strategy: matrix: node: [14, 16, 18] os: [ubuntu-latest, windows-latest] runs-on: ${{ matrix.os }} steps: - uses: actions/setup-node@v3 with: node-version: ${{ matrix.node }} - run: npm test ``` Creates 6 parallel jobs (3 Node versions × 2 OSes). Incremental compilation compiles only changed files: Gradle: ```groovy tasks.withType(JavaCompile) { options.incremental = true options.fork = true options.forkOptions.jvmArgs = ['-Xmx2g'] } ``` TypeScript: ```json { "compilerOptions": { "incremental": true, "tsBuildInfoFile": ".tsbuildinfo" } } ``` Cache .tsbuildinfo between builds. Build caching for task outputs (Gradle): ```groovy buildCache { local { enabled = true } remote(HttpBuildCache) { url = 'https://build-cache.company.com/' push = true } } ``` Distributed compilation spreads work across multiple machines: Distcc (C/C++): ```bash export DISTCC_HOSTS='localhost build1.company.com build2.company.com' make -j8 CC=distcc ``` Icecc alternative supports heterogeneous environments. Gradle distributed builds with build cache enable sharing task outputs across machines in team. Docker layer caching: ```yaml # GitLab build: image: docker:20.10 services: - docker:20.10-dind script: - docker build --cache-from $CI_REGISTRY_IMAGE:latest -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA . ``` BuildKit cache mounts: ```dockerfile RUN --mount=type=cache,target=/root/.m2 mvn package RUN --mount=type=cache,target=/root/.npm npm ci ``` Resource optimization: ```yaml # Allocate more resources for heavy builds build: tags: - high-memory - high-cpu variables: MAVEN_OPTS: "-Xmx4g" GRADLE_OPTS: "-Xmx4g -XX:MaxMetaspaceSize=512m" ``` Skip unnecessary work: ```yaml build: script: - mvn package -DskipTests # Tests run in separate stage rules: - if: '$CI_COMMIT_BRANCH == "main"' - changes: - src/**/* ``` Warmup builds for cold start reduction: ```yaml warmup: stage: .pre script: - mvn dependency:go-offline - docker pull $BASE_IMAGE cache: key: warmup paths: - .m2/repository/ ``` Monitoring and profiling: ```bash # Gradle build scan ./gradlew build --scan # Maven with timing mvn clean install -Dorg.slf4j.simpleLogger.showDateTime=true # Profile Docker build time docker build --progress=plain . ``` Optimization checklist: 1. Cache dependencies aggressively 2. Parallelize independent tasks 3. Enable incremental compilation 4. Use build caching (Gradle, Bazel) 5. Optimize Docker layers 6. Use distributed compilation for C/C++ 7. Skip unnecessary stages 8. Allocate appropriate resources 9. Monitor build times 10. Profile to identify bottlenecks Before optimization, measure baseline. After changes, measure improvements. Focus on slowest stages first for maximum impact. Understanding optimization techniques reduces build time from hours to minutes, improving developer productivity.
Semantic versioning (semver) uses three-part version: MAJOR.MINOR.PATCH (e.g., 2.4.1). Increment MAJOR for incompatible API changes (breaking changes), MINOR for backwards-compatible functionality additions, PATCH for backwards-compatible bug fixes. Version 1.0.0 indicates public API. Versions < 1.0.0 are initial development (anything may change). Examples: 1.0.0 → 1.0.1 (bug fix), 1.0.1 → 1.1.0 (new feature, backwards-compatible), 1.1.0 → 2.0.0 (breaking change). Pre-release versions: 2.0.0-alpha, 2.0.0-beta.1, 2.0.0-rc.1. Build metadata: 1.0.0+20230101. Conventional commits enable automated versioning: ``` feat: add user authentication fix: resolve memory leak in cache docs: update API documentation chore: upgrade dependencies ``` Commit types: feat (MINOR bump), fix (PATCH bump), BREAKING CHANGE (MAJOR bump), docs, style, refactor, test, chore (no version bump). Automated versioning with semantic-release: 1. Install: ```bash npm install --save-dev semantic-release ``` 2. Configuration (.releaserc.json): ```json { "branches": ["main"], "plugins": [ "@semantic-release/commit-analyzer", "@semantic-release/release-notes-generator", "@semantic-release/changelog", "@semantic-release/npm", "@semantic-release/github", "@semantic-release/git" ] } ``` 3. CI/CD integration: ```yaml # GitHub Actions release: runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 - run: npm ci - run: npx semantic-release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} ``` semantic-release analyzes commits, determines version bump, generates changelog, creates Git tag, publishes package, creates GitHub release. Manual versioning with npm version: ```bash npm version patch # 1.0.0 → 1.0.1 npm version minor # 1.0.1 → 1.1.0 npm version major # 1.1.0 → 2.0.0 git push --follow-tags ``` Git tags for releases: ```bash # Create annotated tag git tag -a v1.0.0 -m "Release version 1.0.0" git push origin v1.0.0 # List tags git tag -l # Delete tag git tag -d v1.0.0 git push origin :refs/tags/v1.0.0 ``` Trigger release pipeline on tags: ```yaml # GitLab deploy: stage: deploy script: - ./deploy.sh only: - tags ``` ```yaml # GitHub Actions on: push: tags: - 'v*' ``` Changelog generation: Automatic with conventional-changelog: ```bash npx conventional-changelog -p angular -i CHANGELOG.md -s ``` CHANGELOG.md: ```markdown # Changelog ## [2.0.0] - 2024-01-15 ### Breaking Changes - Changed authentication API ### Features - Added user roles - Implemented OAuth2 ### Bug Fixes - Fixed memory leak in cache - Resolved race condition ## [1.5.0] - 2024-01-01 ... ``` GitHub Release Notes: ```yaml - name: Create Release uses: actions/create-release@v1 with: tag_name: ${{ github.ref }} release_name: Release ${{ github.ref }} body: | ## What's New - Feature 1 - Feature 2 ## Bug Fixes - Fix 1 draft: false prerelease: false ``` Release strategies: 1. Trunk-based: release from main branch, tag releases, hotfixes as patches 2. Release branches: create release/v1.x branch, cherry-pick fixes, main continues development 3. GitFlow: develop branch for development, release branches for stabilization, main for production Version file management: ```yaml # Update version in multiple files - name: Bump version run: | VERSION=$(cat VERSION) sed -i "s/version: .*/version: $VERSION/" helm/Chart.yaml sed -i "s/\"version\": .*/\"version\": \"$VERSION\",/" package.json ``` Best practices: follow semantic versioning strictly, use conventional commits for automation, automate versioning and changelog, tag all releases, maintain CHANGELOG.md, create GitHub/GitLab releases, document breaking changes clearly, communicate version changes to users, use pre-release versions for testing (beta, rc). Understanding versioning and release management ensures clear communication of changes and predictable dependency management.
Build failures troubleshooting starts with console output analyzing error messages. Common causes include missing dependencies (tools not installed on agent, wrong versions), environment issues (missing environment variables, wrong PATH), credential problems (expired or incorrect credentials), network issues (can't reach external services), resource constraints (out of memory, disk space), and flaky tests. Check if builds succeed locally, review recent changes (code, pipeline, Jenkins config), ensure agent has required tools with correct versions, verify credentials are valid and properly scoped. Agent connection issues manifest as agents showing offline or disconnected. Troubleshoot by checking network connectivity (can master reach agent on required ports?), verifying agent Java version matches requirements, reviewing agent logs (on agent machine in remoting directory), checking firewall rules allowing communication, verifying SSH credentials or JNLP connection settings, ensuring agent has sufficient resources (CPU, RAM, disk). For SSH agents, test SSH connection manually. For JNLP agents, check agent logs showing connection attempts. Performance problems include slow UI (Jenkins UI slow to respond), long build queue (jobs waiting for available agents), slow builds (builds taking longer than expected), high resource usage (CPU, memory, disk). Diagnose with performance monitoring: check JVM memory usage (heap, garbage collection), monitor agent utilization (executors busy vs idle), analyze disk I/O (slow disk affecting performance), review build durations identifying slowest stages. Solutions include JVM tuning (increase heap size), adding more agents for parallel execution, optimizing pipelines (caching, parallel stages), cleaning old builds and workspaces, upgrading hardware or moving to more powerful cloud instances. Plugin conflicts occur when plugins are incompatible with each other or with Jenkins core version. Symptoms include Jenkins failing to start, features not working, errors in logs. Troubleshoot by checking Jenkins log (JENKINS_HOME/logs/jenkins.log) for plugin errors, testing in safe mode (starts Jenkins without plugins) to isolate issue, updating plugins to compatible versions, rolling back recently installed plugins, checking plugin dependencies for conflicts. Use Plugin Installation Manager tool to analyze dependencies. Out of memory errors (OOM) indicate JVM heap exhaustion. Symptoms include builds failing with OutOfMemoryError, Jenkins becoming unresponsive, or crashes. Solutions: increase JVM heap size (-Xmx flag), analyze heap dumps (with tools like Eclipse MAT) identifying memory leaks, review plugins for memory leaks, implement build retention policies reducing memory usage, avoid storing large build artifacts in Jenkins. Disk space issues cause builds to fail with "No space left on device". Monitor disk usage with Disk Usage plugin, implement workspace cleanup (cleanWs() in pipelines), configure build retention policies discarding old builds, move artifacts to external storage (Artifactory, S3), clean Docker images if using Docker agents (docker system prune), add more disk space or mount external storage. Credential problems include expired credentials, wrong permissions, or credential not found errors. Verify credential exists with correct ID, check credential scope (Global vs System), test credential manually, ensure user/service account has required permissions, rotate credentials if expired, review audit logs for credential usage. Debugging techniques: enable debug logging for specific components (Manage Jenkins > System Log), use replay feature in Pipeline to test changes without committing, add echo statements for variable values and debug output, use timestamps() wrapper showing execution times, check Jenkins system log for backend errors, review SCM webhook delivery logs checking if webhooks reaching Jenkins. Common solutions: restart Jenkins (fixes many transient issues), rebuild agent (for persistent agent problems), clear browser cache (for UI issues), disable plugins one by one (to isolate plugin conflicts), restore from backup (for configuration corruption). Document troubleshooting steps and solutions in runbooks for team reference. Understanding troubleshooting techniques minimizes downtime and enables quick resolution of Jenkins issues.
GitLab CI/CD pipelines are defined in .gitlab-ci.yml file stored in repository root. This YAML file contains pipeline configuration including stages, jobs, scripts, and conditions. GitLab automatically detects this file and executes the pipeline on commits, merge requests, or manual triggers. The file is version controlled with code enabling Pipeline as Code approach. Basic .gitlab-ci.yml structure includes stages (pipeline phases), jobs (tasks within stages), and scripts (commands to execute). Example: ```yaml stages: - build - test - deploy build_job: stage: build script: - echo "Building application" - make build test_job: stage: test script: - echo "Running tests" - make test ``` GitLab reads .gitlab-ci.yml on each commit, validates syntax, and creates pipeline if valid. Multiple pipelines can run simultaneously for different branches. Changes to .gitlab-ci.yml immediately affect subsequent pipelines. Understanding .gitlab-ci.yml structure is fundamental to GitLab CI/CD.
Jenkins plugins extend core functionality providing integrations with thousands of tools, services, and platforms. Popular plugin categories include SCM plugins (Git, GitHub, GitLab, Bitbucket), build tools (Maven, Gradle, npm), testing frameworks, cloud providers (AWS, Azure, GCP), container platforms (Docker, Kubernetes), notification systems (Slack, email), code quality tools (SonarQube), and artifact repositories (Nexus, Artifactory). Plugins are installed through Jenkins UI (Manage Jenkins > Manage Plugins) or via Jenkins Configuration as Code. Essential plugins include Git plugin for repository integration, Pipeline plugin for Pipeline jobs, Blue Ocean for modern UI, Credentials plugin for secrets management, and Docker plugin for containerized builds. Plugins update frequently, requiring regular updates for security patches and new features. Best practices include keeping plugins updated, using only necessary plugins (too many can cause performance issues), testing plugin updates in non-production first, and using Jenkins Configuration as Code to manage plugins declaratively. Some plugins are bundled with Jenkins, others must be installed separately. Understanding the plugin ecosystem is essential as plugins provide most of Jenkins' practical functionality beyond the core.
npm scripts are defined in package.json under scripts object. Scripts define commands for building, testing, and running applications. Example: ```json { "scripts": { "build": "webpack --mode production", "test": "jest", "start": "node index.js", "lint": "eslint src/" } } ``` Run with npm run <script-name>, e.g., npm run build. Pre and post hooks automatically run before/after scripts: prebuild runs before build, posttest runs after test. CI/CD commonly uses npm ci (clean install from package-lock.json, faster and more reliable than npm install), npm run build, and npm test. Scripts can chain with && or call other scripts with npm run. Benefits include consistent commands across environments, version-controlled build process, easy CI/CD integration. Understanding npm scripts enables automating Node.js project workflows.
Webhooks (GitHub/GitLab/Bitbucket hook) are event-driven triggers where SCM pushes notification to Jenkins immediately on code push, pull request, or tag. Pros: instant feedback (builds start within seconds), efficient (no polling overhead), scalable. Cons: requires Jenkins accessible from SCM (public URL or VPN), needs webhook configuration in SCM, may require webhook authentication. Setup: install GitHub/GitLab plugin, configure webhook in repository settings pointing to https://jenkins.example.com/github-webhook/ or /gitlab-webhook/, optionally secure with webhook secret. SCM polling uses cron syntax to periodically check repository for changes, triggering builds when changes detected. Example: H/5 * * * * checks every 5 minutes, H 0 * * * checks once daily. Pros: works when Jenkins not publicly accessible, simple setup, no SCM configuration needed. Cons: delayed builds (up to poll interval), polling overhead on both Jenkins and SCM, not scalable for many jobs. Use H (hash) instead of fixed times to distribute polling load: H/15 * * * * spreads checks across 15-minute window instead of all jobs polling at once. Manual triggers allow on-demand builds via UI, CLI, or API. Pros: full control over when builds run, useful for testing, release deployments, or gated workflows. Cons: no automation, requires human intervention. Use for: deployment to production (requires approval), ad-hoc testing, investigating build issues. Combine with parameters for flexible manual builds: choice parameters for deployment environment, string parameters for version tags. Scheduled builds (Build periodically) trigger at specified times using cron syntax regardless of code changes. Example: H 2 * * * runs daily around 2 AM, H H * * 0 runs weekly on Sunday. Pros: predictable timing for nightly builds, integration testing, or reports. Cons: runs even without changes (waste of resources), poor feedback loop (long delay between commit and build). Use for: nightly full builds, periodic integration tests, scheduled reports, database backups. Upstream/downstream triggers chain jobs where one job (upstream) triggers another (downstream) on completion. Configure with "Build after other projects are built" trigger or pipeline's build step. Pros: orchestrates complex workflows, ensures dependencies run in order, reuses common jobs. Cons: can create complex dependencies, harder to debug, potential for trigger storms. Use for: multi-stage pipelines (build triggers test triggers deploy), splitting large pipelines into reusable jobs. Best practices: prefer webhooks for instant feedback and efficiency, use polling as fallback when webhooks unavailable, use scheduled builds for time-based operations not triggered by code changes, manual triggers for gated deployments, and avoid complex upstream/downstream chains (prefer single pipeline). Understanding trigger mechanisms enables designing responsive, efficient CI/CD workflows.
Authentication controls who can access Jenkins. Enable security in Configure Global Security. Authentication options include Jenkins' own user database (simple, for small teams), LDAP (enterprise directory integration), Active Directory, SAML (Single Sign-On), OAuth (GitHub, Google, GitLab). For production, integrate with enterprise identity provider for centralized user management and password policies. Enable CAPTCHA for login to prevent brute force attacks. Use strong passwords and enforce password policies. Authorization controls what users can do. Strategy options: Matrix-based security (fine-grained permissions per user/group), Project-based Matrix (permissions per project/folder), Role-Based Strategy (define roles with permissions, assign users to roles). Implement least privilege: regular users get read/build permissions, developers get configure permissions for their projects, only administrators get Jenkins-wide configuration access. Use folders to organize jobs and apply folder-level permissions. Example: QA team folder with QA role having full access, developers having read-only access. Credential management uses Credentials Plugin for encrypted storage. Never put secrets in Jenkinsfile or job configuration. Store credentials in Jenkins credential store with unique IDs, reference by ID in pipelines using withCredentials or environment directive with credentials() function. Credential types: Username with password, Secret text (API tokens), SSH private key, Secret file, Certificate. Implement credential domains to scope credentials to specific jobs or folders. Integrate with external secret management (HashiCorp Vault, AWS Secrets Manager, Azure Key Vault) for enhanced security. Secure master-agent communication: use JNLP4 protocol with encryption, configure agent-to-master access control preventing agents from executing arbitrary commands on master, use SSH with key-based authentication for SSH agents, implement TLS/SSL for web UI and API access. Configure CSRF protection preventing cross-site request forgery attacks. Enable security realm and authorization before exposing Jenkins externally. Network security: place Jenkins behind VPN or firewall, don't expose directly to internet unless necessary, use reverse proxy (Nginx, Apache) with authentication, implement IP whitelisting for webhook endpoints, use webhook authentication (GitHub secret, GitLab token). Enable HTTPS with valid SSL certificate for encrypted communication. Configure security headers (CSP, HSTS, X-Frame-Options). Audit and monitoring: enable audit trail plugin logging all configuration changes and builds, monitor failed login attempts, regularly review user permissions removing inactive users, keep Jenkins and plugins updated for security patches, scan Docker images used in builds for vulnerabilities. Backup Jenkins configuration including credentials (encrypted), test restore procedures. Plugin security: install plugins only from trusted sources, review plugin permissions, keep plugins updated, remove unused plugins. Some plugins have security vulnerabilities, check Jenkins security advisories. Use Jenkins Configuration as Code to manage configuration declaratively with version control and review process. Understanding Jenkins security prevents unauthorized access and protects sensitive data in CI/CD pipelines.
Master optimization starts with adequate hardware: 4+ CPU cores, 8GB+ RAM (more for large installations), fast SSD storage. JVM tuning: set heap size with -Xms and -Xmx flags (typically 50-75% of available RAM, e.g., -Xmx4g for 8GB system), use G1GC garbage collector for better performance: -XX:+UseG1GC. Monitor JVM metrics with monitoring plugins. Don't run builds on master in production, set executor count to 0 to prevent accidental execution. Reduce master load: use folders to organize jobs (improves UI performance), implement job retention policies limiting build history (discard old builds after 30 days or keep last 50 builds), use external artifact storage (Artifactory, Nexus, S3) instead of storing in Jenkins, regularly clean up orphaned workspaces, limit plugin count removing unused plugins. Enable master-agent security to prevent agents from overloading master. Agent scaling strategies: use cloud agents (AWS, Azure, GCP, Kubernetes) for automatic scaling based on queue depth, implement agent labeling allowing pipelines to request specific capabilities (docker, maven, nodejs), configure appropriate executor count per agent (typically CPU core count), use ephemeral agents (Docker, Kubernetes pods) for isolation and cleanup, monitor agent utilization identifying underused or overloaded agents. Pipeline optimization includes caching dependencies (Maven .m2, npm node_modules, Docker layers) between builds using shared volumes or cache plugins, parallel execution for independent tasks: parallel { stage('Unit Tests') {...} stage('Integration Tests') {...} }, skip unnecessary stages with when conditions avoiding work when possible, use lightweight checkouts for Multibranch pipelines reducing SCM polling overhead, implement timeouts preventing hanging builds from blocking agents. Workspace management: clean workspaces regularly with cleanWs() to free disk space, use shared workspaces for related jobs avoiding duplication, mount external storage for large artifacts or dependencies, implement workspace retention policies. Monitor disk usage with disk-usage plugin, set up alerts for low disk space. Build queue optimization: configure quiet period reducing rapid successive builds for same job, use throttle concurrent builds plugin limiting simultaneous builds per job or category, implement build priorities for critical jobs, analyze build queue with queue monitoring, increase agent pool if queue consistently backed up. Database optimization: Jenkins stores configuration and metadata in XML files which can become slow. Consider Jenkins HA or CloudBees Core for better performance with database backend. Regularly backup and compact JENKINS_HOME. Use filesystem compression for JENKINS_HOME. Monitoring and observability: implement Prometheus monitoring for Jenkins metrics (queue length, executor usage, build duration), set up Grafana dashboards for visualization, use Jenkins Metrics plugin, configure alerts for anomalies (long queues, agent disconnections, build failures), analyze build trends identifying bottlenecks. Best practices: regularly review and optimize pipelines, implement build caching, use pipeline libraries for code reuse, avoid checkout multiple times in pipeline, use stash/unstash for small file transfers between stages instead of full workspace, profile builds identifying slow steps, implement incremental builds when possible. Understanding performance optimization ensures Jenkins scales efficiently for large engineering organizations.
Critical data to backup includes JENKINS_HOME directory containing jobs (jobs/ directory with job configurations), builds (builds/ directory with build history and artifacts, though often excluded due to size), plugins (plugins/ directory), system configuration (config.xml, credentials.xml, secrets/), user content (userContent/), and workspace (workspace/ rarely backed up as it's recreatable). Essential are job configurations, credentials, and system configuration; build history and artifacts are optional (can be large). Backup strategies: full backup copies entire JENKINS_HOME periodically (weekly), incremental backup copies only changes since last backup (daily), configuration-only backup excludes builds and workspaces (small, frequent). Use thinBackup plugin for automated scheduled backups with retention policies, or Jenkins Backup Plugin, or filesystem-level backups (LVM snapshots, AWS EBS snapshots). Store backups offsite (S3, Azure Blob, separate datacenter) for disaster recovery. Configuration as Code approach: use Jenkins Configuration as Code (JCasC) plugin storing configuration as YAML in version control, Job DSL Plugin or Pipeline for job definitions as code, Credentials stored in external secret management. This enables treating Jenkins as cattle not pets, recreating from code rather than restoring from backup. Combine with backup strategy for defense in depth. Backup schedule and retention: configuration backups daily with 30-day retention, full backups weekly with 4-week retention, monthly backups kept for 12 months. Automate with cron or Jenkins job. Test backups regularly (monthly) by restoring to test environment verifying configuration and jobs work correctly. Untested backups are useless. What to exclude from backups: workspace directories (recreatable from SCM), fingerprints (not critical), large build artifacts (store in artifact repository instead), old build history (implement retention policy). This reduces backup size and time significantly. Disaster recovery procedures: install Jenkins on new server matching version of backed-up instance, stop Jenkins service, restore JENKINS_HOME from backup overwriting new installation, install required plugins (from backed-up plugins list or JCasC), start Jenkins service, verify jobs run correctly, reconfigure agents (agents will reconnect), update DNS or load balancer to point to new instance. Document step-by-step DR procedures with screenshots. High Availability options: Jenkins HA with active-passive setup sharing JENKINS_HOME on network storage, or CloudBees Jenkins Enterprise with HA features. Shared filesystem (NFS, EFS) enables fast failover. Test failover procedures regularly. For critical installations, implement HA rather than relying solely on backups. Backup automation example: ```groovy pipeline { agent any triggers { cron('H 2 * * *') } // Daily at ~2 AM stages { stage('Backup') { steps { sh ''' tar -czf jenkins-backup-$(date +%Y%m%d).tar.gz \ --exclude='workspace/*' \ --exclude='builds/*/archive' \ $JENKINS_HOME ''' sh 'aws s3 cp jenkins-backup-*.tar.gz s3://backups/jenkins/' sh 'find . -name "jenkins-backup-*.tar.gz" -mtime +30 -delete' } } } } ``` Understanding backup and DR strategies ensures business continuity and quick recovery from failures, critical for organizations relying on Jenkins for software delivery.
Blue Ocean is a modern, redesigned user interface for Jenkins focused on Pipeline visualization and creation. It provides a fresh, contemporary UI replacing Jenkins' classic interface which has remained largely unchanged since 2011. Blue Ocean offers intuitive visual pipeline editor, real-time pipeline visualization, personalized dashboard, and improved usability especially for Pipeline-centric workflows. Key features include visual pipeline editor allowing creating declarative pipelines through drag-and-drop interface without writing Groovy code directly. The editor automatically generates Jenkinsfile stored in repository. Pipeline visualization shows stages as horizontal blocks with parallel stages displayed vertically, color-coded by status (blue success, red failure, gray not run). Click stages to see logs for that specific stage, improving debugging experience. Pipeline run details page shows comprehensive view of pipeline execution with stage-level logs, test results, artifacts, and change sets. Timeline view shows duration of each stage helping identify bottlenecks. Branch and pull request discovery automatically creates pipelines for branches and PRs in Multibranch projects. Personalized dashboard shows user's favorites and recent runs with filters. Benefits over classic UI: cleaner, more intuitive interface reducing cognitive load, better pipeline visualization understanding complex pipelines at a glance, improved navigation with breadcrumbs and search, visual editor lowering barrier to pipeline creation for less technical users, faster access to logs and test results with stage-level filtering, responsive design working better on different screen sizes. When to use Blue Ocean: recommended for pipeline-centric workflows (Declarative and Scripted pipelines), organizations transitioning to Pipeline from Freestyle, teams wanting better visualization and UX, use cases requiring visual pipeline editor. Blue Ocean focuses on pipelines and doesn't provide full feature parity with classic UI for some administrative tasks. When to use classic UI: required for Freestyle project management (Blue Ocean primarily supports pipelines), system administration tasks (plugin management, security configuration, agent management), certain plugins only integrate with classic UI, teams preferring traditional interface or having custom classic UI extensions. Installation: install Blue Ocean plugin from Plugin Manager. Access via link in classic UI sidebar or directly at /blue URL path. Both interfaces coexist, switch between them as needed. Blue Ocean reads same Jenkins data as classic UI, so no migration necessary. New installations can start with Blue Ocean for pipeline work while using classic UI for administration. Limitations: some advanced pipeline features require manual Jenkinsfile editing (Blue Ocean editor supports common patterns but not full syntax), not all plugins have Blue Ocean integration, some users prefer classic UI's information density. Blue Ocean development slowed recently as Jenkins community focuses on classic UI improvements. Understanding Blue Ocean enables leveraging modern UI for improved pipeline creation and visualization while knowing when classic UI is more appropriate.
GitHub Actions workflows are stored in .github/workflows/ directory as YAML files. Each file defines a separate workflow that can be triggered by various events (push, pull request, schedule, manual). Multiple workflow files enable organizing different CI/CD processes (build, test, deploy, release) separately while maintaining them in the same repository. Basic workflow structure: ```yaml name: CI on: push: branches: [ main ] pull_request: branches: [ main ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Run tests run: npm test ``` Workflow files must be in .github/workflows/ to be detected by GitHub. File names can be anything (ci.yml, deploy.yml, release.yml) with .yml or .yaml extension. GitHub automatically discovers and executes workflows based on their triggers. Changes to workflow files affect subsequent runs. Understanding workflow file location and structure is fundamental to GitHub Actions.
Gradle uses Groovy or Kotlin DSL for build configuration. Build.gradle defines project structure: ```groovy plugins { id 'java' id 'org.springframework.boot' version '2.7.0' id 'io.spring.dependency-management' version '1.0.11.RELEASE' } group = 'com.example' version = '1.0.0' sourceCompatibility = '11' repositories { mavenCentral() maven { url 'https://nexus.company.com/repository/maven-public/' credentials { username = System.getenv('NEXUS_USER') password = System.getenv('NEXUS_PASSWORD') } } } dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.postgresql:postgresql:42.3.1' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.junit.jupiter:junit-jupiter' } tasks.named('test') { useJUnitPlatform() } ``` Dependency configurations: implementation (compile and runtime, not exposed to consumers), api (exposed to consumers in library projects), compileOnly (compile time only, like Maven provided), runtimeOnly (runtime only), testImplementation, testRuntimeOnly. Dependency constraints and versions: ```groovy dependencies { implementation 'com.google.guava:guava:31.0-jre' implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.0' // Constraint (doesn't add dependency, only sets version if included transitively) constraints { implementation 'org.apache.commons:commons-lang3:3.12.0' } } ``` Dependency resolution strategies: ```groovy configurations.all { resolutionStrategy { // Force specific version force 'com.google.guava:guava:31.0-jre' // Fail on version conflict failOnVersionConflict() // Cache for 24 hours cacheDynamicVersionsFor 24, 'hours' } } ``` Custom tasks: ```groovy task hello { doLast { println 'Hello from Gradle!' } } task copyDocs(type: Copy) { from 'src/docs' into 'build/docs' } task buildDockerImage(type: Exec) { commandLine 'docker', 'build', '-t', "myapp:${version}", '.' } task dist(type: Zip) { from 'build/libs' archiveFileName = "myapp-${version}.zip" } ``` Multi-project builds organize related projects: ``` root-project/ ├── settings.gradle ├── build.gradle ├── app/ │ └── build.gradle ├── library/ │ └── build.gradle └── common/ └── build.gradle ``` Settings.gradle: ```groovy rootProject.name = 'my-application' include 'app', 'library', 'common' ``` Root build.gradle: ```groovy subprojects { apply plugin: 'java' repositories { mavenCentral() } dependencies { testImplementation 'junit:junit:4.13.2' } } ``` App build.gradle: ```groovy dependencies { implementation project(':library') implementation project(':common') } ``` Performance optimization: Gradle daemon (enabled by default, reuses JVM across builds), build cache (caches task outputs): ```groovy buildCache { local { enabled = true } remote(HttpBuildCache) { url = 'https://cache.company.com/' credentials { username = System.getenv('BUILD_CACHE_USER') password = System.getenv('BUILD_CACHE_PASSWORD') } push = true } } ``` Parallel execution: ```bash ./gradlew build --parallel ``` Configuration cache (faster configuration phase): ```bash ./gradlew build --configuration-cache ``` CI/CD integration: ```yaml # GitHub Actions - uses: gradle/gradle-build-action@v2 with: arguments: build cache-read-only: false ``` Publishing artifacts: ```groovy plugins { id 'maven-publish' } publishing { publications { mavenJava(MavenPublication) { from components.java } } repositories { maven { url = version.endsWith('SNAPSHOT') ? 'https://nexus.company.com/repository/maven-snapshots/' : 'https://nexus.company.com/repository/maven-releases/' credentials { username = System.getenv('NEXUS_USER') password = System.getenv('NEXUS_PASSWORD') } } } } ``` Best practices: use Gradle wrapper, enable build cache, use parallel execution, leverage incremental compilation, cache dependencies in CI/CD, use configuration cache for large projects, profile builds (./gradlew build --scan), modularize large projects. Understanding Gradle enables efficient, flexible build automation.
npm (Node Package Manager) manages JavaScript dependencies defined in package.json: ```json { "name": "my-app", "version": "1.0.0", "description": "My application", "main": "index.js", "scripts": { "start": "node index.js", "dev": "nodemon index.js", "build": "webpack --mode production", "test": "jest", "lint": "eslint src/", "format": "prettier --write src/" }, "dependencies": { "express": "^4.18.0", "dotenv": "^16.0.0" }, "devDependencies": { "jest": "^28.0.0", "nodemon": "^2.0.0", "eslint": "^8.0.0" }, "engines": { "node": ">=16.0.0", "npm": ">=8.0.0" } } ``` Dependency types: dependencies (production), devDependencies (development only, not installed in production with npm install --production), peerDependencies (required by library but provided by consumer), optionalDependencies (install if possible, failure doesn't stop installation). Version ranges: "^4.18.0" (compatible with 4.x.x, >= 4.18.0 < 5.0.0), "~4.18.0" (patch updates, >= 4.18.0 < 4.19.0), "4.18.0" (exact version), "*" (any version). Caret (^) is default, allows minor and patch updates. Lock files (package-lock.json) ensure reproducible installs by recording exact versions of all dependencies and transitive dependencies: ```json { "name": "my-app", "version": "1.0.0", "lockfileVersion": 2, "requires": true, "packages": { "node_modules/express": { "version": "4.18.2", "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", "integrity": "sha512-..." } } } ``` Commit lock file to version control. npm commands: npm install (install dependencies from package.json), npm ci (clean install from lock file, faster and more reliable for CI/CD), npm update (update packages within version ranges), npm outdated (show outdated packages), npm audit (check for vulnerabilities), npm audit fix (automatically fix vulnerabilities). npm scripts enable automation: ```json "scripts": { "prebuild": "npm run clean", "build": "webpack --mode production", "postbuild": "npm run test", "clean": "rm -rf dist", "test": "jest --coverage", "test:watch": "jest --watch", "deploy": "npm run build && firebase deploy" } ``` pre and post hooks automatically run before/after main script. Yarn alternative offers faster installs, better security, offline mode. Yarn commands: yarn install, yarn add, yarn upgrade. Yarn lock file (yarn.lock) similar to package-lock.json. Modern npm (v7+) matches yarn performance. CI/CD integration: ```yaml # GitHub Actions - uses: actions/setup-node@v3 with: node-version: '18' cache: 'npm' - run: npm ci - run: npm run build - run: npm test ``` Private registry configuration (.npmrc): ``` registry=https://registry.npmjs.org/ @myorg:registry=https://npm.company.com/ //npm.company.com/:_authToken=${NPM_TOKEN} ``` Publishing packages: ```bash npm version patch # Increment version npm publish ``` Security practices: npm audit regularly, update dependencies, use exact versions for critical dependencies, scan for known vulnerabilities with Snyk or Dependabot, avoid running npm as root, use .npmrc for private registries, enable 2FA for npm account, use npm ci in CI/CD (ignores package.json, only uses lock file). Understanding npm enables efficient Node.js project management and automation.
Artifact repositories manage binary artifacts (JARs, npm packages, Docker images, etc.) providing centralized storage, versioning, and distribution. Major solutions: JFrog Artifactory, Sonatype Nexus, AWS CodeArtifact, Azure Artifacts. Repository types: 1. Hosted repositories store artifacts built internally: - maven-releases (release versions) - maven-snapshots (development snapshots) - npm-private (private npm packages) - docker-private (private Docker images) 2. Proxy repositories cache artifacts from remote sources: - maven-central (proxies Maven Central) - npm-registry (proxies npmjs.com) - docker-hub (proxies Docker Hub) Provides faster downloads, reliability when external sources unavailable, control over external dependencies. 3. Group repositories combine multiple repositories: - maven-public (groups maven-releases, maven-snapshots, maven-central) - npm-group (groups npm-private, npm-registry) Simplifies client configuration (one URL for all sources). Nexus configuration example: Maven client configuration (pom.xml): ```xml <repositories> <repository> <id>nexus</id> <url>https://nexus.company.com/repository/maven-public/</url> </repository> </repositories> <distributionManagement> <repository> <id>nexus-releases</id> <url>https://nexus.company.com/repository/maven-releases/</url> </repository> <snapshotRepository> <id>nexus-snapshots</id> <url>https://nexus.company.com/repository/maven-snapshots/</url> </snapshotRepository> </distributionManagement> ``` Credentials in settings.xml (~/.m2/settings.xml): ```xml <settings> <servers> <server> <id>nexus-releases</id> <username>${env.NEXUS_USER}</username> <password>${env.NEXUS_PASSWORD}</password> </server> <server> <id>nexus-snapshots</id> <username>${env.NEXUS_USER}</username> <password>${env.NEXUS_PASSWORD}</password> </server> </servers> </settings> ``` npm configuration (.npmrc): ``` registry=https://nexus.company.com/repository/npm-group/ //nexus.company.com/repository/npm-group/:_authToken=${NPM_TOKEN} ``` Docker registry configuration: ```bash docker login nexus.company.com:5000 docker tag myapp:latest nexus.company.com:5000/myapp:1.0.0 docker push nexus.company.com:5000/myapp:1.0.0 ``` Artifact publishing in CI/CD: Maven: ```yaml deploy: stage: deploy script: - mvn deploy -DskipTests only: - main ``` npm: ```yaml publish: stage: publish script: - echo "//nexus.company.com/repository/npm-private/:_authToken=${NPM_TOKEN}" > .npmrc - npm publish ``` Docker: ```yaml push_image: stage: publish script: - docker build -t nexus.company.com:5000/myapp:${CI_COMMIT_SHA} . - docker push nexus.company.com:5000/myapp:${CI_COMMIT_SHA} ``` Promotion workflows move artifacts between repositories (e.g., staging to production): 1. Build pushes to staging repository 2. Run tests against staging artifacts 3. Manual approval 4. Promote to production repository Nexus promotion (using REST API): ```bash curl -u admin:password -X POST \ "https://nexus.company.com/service/rest/v1/staging/promote" \ -H "Content-Type: application/json" \ -d '{ "data": { "stagedRepositoryId": "staging-repo", "targetRepositoryId": "releases" } }' ``` Artifactory promotion: ```bash jfrog rt build-promote myapp 1.0.0 production-repo --status="Released" ``` Cleanup policies prevent disk space exhaustion: - Remove snapshots older than 30 days - Keep only last 10 versions of each artifact - Delete unused artifacts after 90 days Access control: - Role-based permissions (developer, QA, ops) - Repository-level permissions - Read/write/deploy permissions - Anonymous access for public artifacts Best practices: use group repositories for clients, separate hosted repositories by maturity (snapshot/release), implement cleanup policies, use promotion workflows for production, enable security scanning, monitor storage usage, backup regularly, use HTTPS, implement access control, integrate with SSO. Understanding artifact repository management enables efficient, secure artifact lifecycle management in enterprise CI/CD.
Multi-stage builds separate build and runtime environments, creating smaller, more secure images by excluding build tools and intermediate artifacts from final image. Basic multi-stage example (Node.js): ```dockerfile # Build stage FROM node:18 AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build RUN npm prune --production # Production stage FROM node:18-alpine WORKDIR /app COPY --from=builder /app/dist ./dist COPY --from=builder /app/node_modules ./node_modules COPY package*.json ./ USER node EXPOSE 3000 CMD ["node", "dist/index.js"] ``` Benefits: builder stage includes devDependencies and build tools, production stage only contains runtime dependencies and compiled code, final image significantly smaller (50-80% reduction). Java example with Maven: ```dockerfile # Build stage FROM maven:3.8-jdk-11 AS builder WORKDIR /app COPY pom.xml . RUN mvn dependency:go-offline COPY src ./src RUN mvn package -DskipTests # Production stage FROM openjdk:11-jre-slim WORKDIR /app COPY --from=builder /app/target/*.jar app.jar USER nobody EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"] ``` Layer caching optimization - order instructions from least to most frequently changing: ```dockerfile FROM node:18 AS builder WORKDIR /app # Cache layer 1: package files (rarely change) COPY package*.json ./ RUN npm ci # Cache layer 2: source code (changes frequently) COPY . . RUN npm run build # Production FROM node:18-alpine WORKDIR /app COPY --from=builder /app/dist ./dist COPY --from=builder /app/node_modules ./node_modules ``` Changes to source code don't invalidate dependency installation layer. BuildKit features (Docker 18.09+): ```dockerfile # syntax=docker/dockerfile:1 FROM node:18 AS builder WORKDIR /app # Mount cache for npm RUN --mount=type=cache,target=/root/.npm \ npm ci # Mount source without copying (faster) RUN --mount=type=bind,source=package.json,target=package.json \ --mount=type=bind,source=src,target=src \ npm run build ``` Security best practices: ```dockerfile FROM node:18-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build # Production with security FROM node:18-alpine # Create non-root user RUN addgroup -g 1001 -S nodejs && \ adduser -S nodejs -u 1001 WORKDIR /app # Set ownership COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules COPY --chown=nodejs:nodejs package*.json ./ # Switch to non-root USER nodejs EXPOSE 3000 CMD ["node", "dist/index.js"] ``` Security measures: use minimal base images (alpine), run as non-root user, scan for vulnerabilities, don't include secrets in image, use .dockerignore to exclude unnecessary files. .dockerignore: ``` node_modules npm-debug.log .git .env *.md .DS_Store tests .github ``` CI/CD integration (GitLab): ```yaml build_image: stage: build image: docker:20.10 services: - docker:20.10-dind variables: DOCKER_TLS_CERTDIR: "/certs" DOCKER_BUILDKIT: 1 script: - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - | docker build \ --cache-from $CI_REGISTRY_IMAGE:latest \ --tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA \ --tag $CI_REGISTRY_IMAGE:latest \ . - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA - docker push $CI_REGISTRY_IMAGE:latest ``` GitHub Actions with caching: ```yaml - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 - name: Build and push uses: docker/build-push-action@v4 with: context: . push: true tags: | myregistry.com/myapp:${{ github.sha }} myregistry.com/myapp:latest cache-from: type=gha cache-to: type=gha,mode=max ``` Image scanning: ```yaml - name: Scan image uses: aquasecurity/trivy-action@master with: image-ref: 'myapp:${{ github.sha }}' format: 'sarif' output: 'trivy-results.sarif' severity: 'CRITICAL,HIGH' ``` Best practices: use multi-stage builds for all languages, order instructions for optimal caching, use minimal base images, run as non-root, scan images for vulnerabilities, leverage BuildKit caching, use .dockerignore, tag images with commit SHA and semantic version, implement image signing. Understanding Docker optimization creates secure, efficient container images for production deployments.
Declarative Pipeline uses structured, opinionated syntax starting with pipeline block. Required sections include agent and stages. Syntax is rigid but provides better validation, easier learning curve, and built-in best practices. Declarative example: ```groovy pipeline { agent any environment { APP_VERSION = '1.0.0' } stages { stage('Build') { steps { sh 'mvn clean package' } } stage('Test') { steps { sh 'mvn test' } } } post { success { echo 'Success!' } } } ``` Scripted Pipeline uses flexible Groovy-based syntax starting with node block. Allows arbitrary Groovy code, variables, functions, and control flow. More powerful but requires more Groovy knowledge. Scripted example: ```groovy node { def appVersion = '1.0.0' stage('Build') { sh 'mvn clean package' } stage('Test') { try { sh 'mvn test' } catch (Exception e) { echo "Tests failed: ${e.message}" throw e } } if (env.BRANCH_NAME == 'main') { stage('Deploy') { sh './deploy.sh' } } } ``` Key differences: Declarative uses pipeline block vs Scripted's node block, Declarative has predefined structure vs Scripted's free-form, Declarative validates syntax before execution vs Scripted fails at runtime, Declarative supports resume from stage vs Scripted restarts from beginning, Declarative has when directive vs Scripted uses if statements, Declarative uses post for cleanup vs Scripted uses try-catch-finally. Flexibility: Scripted allows complex logic (loops, conditionals, functions) that's cumbersome in Declarative. Declarative can embed Groovy in script step but encourages using predefined directives. Example complex logic better suited for Scripted: ```groovy def envs = ['dev', 'staging', 'prod'] for (env in envs) { stage("Deploy to ${env}") { if (shouldDeploy(env)) { deployTo(env) } } } ``` When to use Declarative: recommended for most pipelines, new pipeline development, teams with limited Groovy experience, when you want structure and validation, simpler maintenance requirements, need restart from stage feature. When to use Scripted: complex dynamic logic (loops, advanced conditionals), need full Groovy capabilities, existing Scripted pipelines (no need to rewrite if working), highly customized workflows not fitting Declarative model. Mixed approach: use Declarative as default with script blocks for complex logic when needed: ```groovy pipeline { agent any stages { stage('Complex Logic') { steps { script { def result = complexGroovyFunction() if (result.success) { echo "Success: ${result.value}" } } } } } } ``` Best practices: prefer Declarative for consistency and maintainability, use script blocks sparingly in Declarative for complex logic, document why Scripted is used if chosen, consider Shared Libraries for complex logic instead of inline Scripted code. Understanding both syntaxes enables choosing appropriate approach and maintaining diverse pipeline codebases.
Architecture comparison: Jenkins: self-hosted, master-agent architecture. Master manages configuration and schedules jobs, agents execute builds. Flexible but requires infrastructure management. Plugin-based extensibility with 1500+ plugins. Highly customizable but steeper learning curve. GitLab CI/CD: integrated with GitLab (self-hosted or cloud). Runners execute jobs, managed similarly to Jenkins agents. Tight GitLab integration (issue tracking, merge requests, security scanning) providing complete DevOps platform. Can use shared runners on GitLab.com or self-hosted runners. GitHub Actions: cloud-native, integrated with GitHub. GitHub-hosted runners or self-hosted runners. Serverless execution model - no master server to manage. Marketplace with thousands of reusable actions. Designed for GitHub ecosystem. Configuration: Jenkins: Jenkinsfile (Declarative or Scripted Pipeline) or GUI configuration. Groovy-based, powerful but requires learning curve. Supports complex logic with full programming capabilities. GitLab CI/CD: .gitlab-ci.yml YAML file. Straightforward syntax with stages, jobs, rules. Includes/extends for reusability. Good balance of simplicity and power. GitHub Actions: .github/workflows/*.yml YAML files. Event-driven workflows. Marketplace actions reduce configuration complexity. Matrix strategy for multi-configuration testing. Features comparison: Jenkins: - Mature ecosystem with extensive plugins - Blue Ocean modern UI - Distributed builds - Pipeline as Code - Shared Libraries - Approval gates - Cons: requires maintenance, setup complexity GitLab CI/CD: - Integrated with GitLab SCM - Auto DevOps for automated pipelines - Built-in container registry - Security scanning (SAST, DAST, dependency scanning) - Kubernetes integration - Review apps for preview environments - Cons: tied to GitLab ecosystem GitHub Actions: - Native GitHub integration - Extensive marketplace - Matrix builds - Reusable workflows - Environments with protection rules - GitHub Packages integration - Cons: GitHub-specific, limited runner customization compared to Jenkins Scalability: Jenkins: highly scalable with agent pools, cloud agents (AWS, Azure, GCP, Kubernetes), autoscaling support. Requires managing infrastructure but offers maximum control. GitLab CI/CD: scalable with runner autoscaling (Docker Machine, Kubernetes), shared runners on GitLab.com handle scaling automatically. Good balance of managed and self-hosted options. GitHub Actions: GitHub-hosted runners scale automatically (managed by GitHub), self-hosted runners require manual scaling but support autoscaling with custom solutions. Cloud-native approach simplifies scaling. Pricing: Jenkins: free and open-source but requires infrastructure costs (servers, maintenance, support). Total cost includes hosting, maintenance time, support. GitLab CI/CD: free tier with shared runners (400 compute minutes/month for GitLab.com free plan), paid tiers offer more minutes and features, self-hosted GitLab CE is free (unlimited builds), EE has premium features. Costs: subscription or self-hosting infrastructure. GitHub Actions: free for public repositories, private repositories get 2000-3000 minutes/month (varies by plan), additional minutes purchasable. Self-hosted runners free but require infrastructure. Costs: GitHub subscription and runner infrastructure. Use cases: Jenkins best for: - Organizations with existing Jenkins investment - Complex, customized workflows requiring flexibility - On-premise requirements - Multi-SCM environments (Git, SVN, Mercurial) - Need for specific plugins - Maximum control over infrastructure GitLab CI/CD best for: - Teams using GitLab for SCM - Organizations wanting complete DevOps platform - Need for integrated security scanning - Kubernetes-native workflows - Review apps and preview environments - Self-hosted with good scaling GitHub Actions best for: - GitHub-based projects - Open-source projects (free unlimited minutes) - Minimal infrastructure management desired - Leveraging marketplace ecosystem - Quick setup and getting started - Modern, cloud-native approach Migration considerations: - Jenkins to GitLab/GitHub: port Jenkinsfile to YAML, adapt plugins to native features or marketplace actions - GitLab to GitHub: convert .gitlab-ci.yml to workflows, adapt GitLab-specific features - All platforms support Pipeline as Code, artifacts, caching, parallel execution Decision factors: existing toolchain, team expertise, hosting preference (cloud vs self-hosted), budget, required features, integration needs, scalability requirements. Understanding platform differences enables choosing appropriate CI/CD solution for specific organizational needs.
Multi-stage pipelines organize CI/CD into logical phases with progression gates. Example GitLab: ```yaml stages: - build - test - security - staging - production build: stage: build script: make build artifacts: paths: [dist/] unit_test: stage: test script: make test integration_test: stage: test script: make integration-test needs: [build] sast: stage: security script: semgrep scan deploy_staging: stage: staging script: deploy staging environment: name: staging url: https://staging.example.com deploy_production: stage: production script: deploy production when: manual only: [main] environment: name: production url: https://example.com ``` Pipeline orchestration coordinates multiple pipelines across repositories. Parent-child pipelines in GitLab: ```yaml # Parent pipeline trigger_backend: stage: test trigger: project: team/backend strategy: depend # Wait for triggered pipeline trigger_frontend: stage: test trigger: project: team/frontend ``` GitHub Actions workflow dependencies: ```yaml # Backend workflow triggers frontend - name: Trigger Frontend uses: peter-evans/repository-dispatch@v2 with: token: ${{ secrets.PAT }} repository: org/frontend event-type: backend-updated ``` Trunk-based development workflow with short-lived feature branches: ```yaml # GitHub Actions on: push: branches: [main] pull_request: branches: [main] jobs: pr-checks: if: github.event_name == 'pull_request' runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - run: npm test - run: npm run lint deploy: if: github.ref == 'refs/heads/main' needs: pr-checks runs-on: ubuntu-latest steps: - run: ./deploy.sh production ``` Feature flag integration for trunk-based development: ```yaml deploy: stage: deploy script: - deploy --feature-flags=new-ui:50% environment: production ``` Blue-green deployment pattern: ```yaml # GitLab stages: - deploy - switch - cleanup deploy_green: stage: deploy script: - deploy green-environment - run-smoke-tests green environment: name: production-green switch_traffic: stage: switch script: - switch-load-balancer blue green when: manual cleanup_blue: stage: cleanup script: - cleanup blue-environment when: manual ``` Canary deployment with progressive rollout: ```yaml # GitHub Actions - name: Deploy Canary run: | kubectl set image deployment/app app=myapp:${{ github.sha }} kubectl patch deployment app -p '{"spec":{"replicas":1}}' - name: Monitor Canary run: ./monitor-metrics.sh timeout-minutes: 10 - name: Full Rollout if: success() run: kubectl scale deployment/app --replicas=10 - name: Rollback if: failure() run: kubectl rollout undo deployment/app ``` Rollback mechanism: ```yaml rollback_production: stage: rollback script: - kubectl rollout undo deployment/app when: manual only: [main] environment: name: production action: rollback ``` Approval gates for production: ```yaml # GitLab deploy_prod: stage: production script: deploy production when: manual only: [main] environment: name: production # GitHub Actions production: runs-on: ubuntu-latest environment: name: production required-reviewers: ['ops-team'] steps: - run: ./deploy.sh ``` Post-deployment verification: ```yaml verify_deployment: stage: verify script: - curl https://production.example.com/health - run-integration-tests production retry: max: 2 when: script_failure ``` Monitoring integration: ```yaml - name: Send Deployment Event run: | curl -X POST https://monitoring.example.com/api/events \ -d '{"type":"deployment","version":"${{ github.sha }}"}' - name: Check Error Rate run: | ERROR_RATE=$(query-prometheus) if [ $ERROR_RATE -gt 1 ]; then echo "High error rate detected" exit 1 fi ``` Dynamic environment creation (review apps): ```yaml review_app: stage: review script: - deploy review-app-$CI_MERGE_REQUEST_IID environment: name: review/$CI_MERGE_REQUEST_IID url: https://review-$CI_MERGE_REQUEST_IID.example.com on_stop: stop_review_app only: - merge_requests stop_review_app: stage: review script: - destroy review-app-$CI_MERGE_REQUEST_IID environment: name: review/$CI_MERGE_REQUEST_IID action: stop when: manual ``` Understanding advanced patterns enables building sophisticated CI/CD workflows supporting modern development practices and deployment strategies.
Secrets management is critical for CI/CD security. Never commit secrets to repositories. Use platform secret managers: GitLab CI/CD variables (masked and protected): ```yaml variables: PUBLIC_VAR: "visible" # SECRET_VAR configured in GitLab UI (Settings > CI/CD > Variables) # Mark as "Masked" to hide in logs # Mark as "Protected" to limit to protected branches deploy: script: - deploy --api-key=$SECRET_VAR ``` GitHub Actions secrets: ```yaml steps: - name: Deploy run: ./deploy.sh env: API_KEY: ${{ secrets.API_KEY }} DATABASE_URL: ${{ secrets.DATABASE_URL }} ``` External secret management integration (HashiCorp Vault): ```yaml # GitLab vault_secrets: image: vault:latest script: - export VAULT_TOKEN=$(vault login -method=jwt role=my-role jwt=$CI_JOB_JWT -field=token) - export SECRET=$(vault kv get -field=value secret/api-key) - deploy --api-key=$SECRET ``` ```yaml # GitHub Actions with Vault - name: Import Secrets uses: hashicorp/vault-action@v2 with: url: https://vault.example.com method: jwt role: github-actions secrets: | secret/data/api key | API_KEY secret/data/db password | DB_PASS ``` Rotate secrets regularly, use short-lived tokens when possible, limit secret scope (per-project, per-environment), audit secret access. Artifact security ensures build outputs are safe. Sign artifacts for verification: ```yaml # Sign container images - name: Sign Image run: | cosign sign --key cosign.key myapp:${{ github.sha }} ``` Verify artifact integrity: ```yaml - name: Verify Signature run: | cosign verify --key cosign.pub myapp:${{ github.sha }} ``` Vulnerability scanning detects security issues: Container scanning: ```yaml # GitLab (built-in) container_scanning: stage: test variables: DOCKER_IMAGE: myapp:latest allow_failure: true # GitHub Actions with Trivy - name: Scan Image uses: aquasecurity/trivy-action@master with: image-ref: 'myapp:${{ github.sha }}' format: 'sarif' output: 'trivy-results.sarif' severity: 'CRITICAL,HIGH' - name: Upload Results uses: github/codeql-action/upload-sarif@v2 with: sarif_file: 'trivy-results.sarif' ``` Dependency scanning: ```yaml # GitLab (built-in) dependency_scanning: stage: test artifacts: reports: dependency_scanning: gl-dependency-scanning-report.json # GitHub Actions with Snyk - name: Snyk Test uses: snyk/actions/node@master env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} with: args: --severity-threshold=high ``` Static Application Security Testing (SAST): ```yaml # GitLab sast: stage: test artifacts: reports: sast: gl-sast-report.json # GitHub Actions with Semgrep - name: Semgrep uses: returntocorp/semgrep-action@v1 with: config: auto ``` Dynamic Application Security Testing (DAST): ```yaml dast: stage: security image: owasp/zap2docker-stable script: - zap-baseline.py -t https://staging.example.com ``` Supply chain security protects against compromised dependencies: Dependency pinning (lock files): ```yaml - name: Verify Lock File run: | if ! git diff --exit-code package-lock.json; then echo "Lock file changed, failing build" exit 1 fi ``` Software Bill of Materials (SBOM): ```yaml - name: Generate SBOM uses: anchore/sbom-action@v0 with: image: myapp:${{ github.sha }} format: cyclonedx output-file: sbom.json - name: Upload SBOM uses: actions/upload-artifact@v3 with: name: sbom path: sbom.json ``` Code signing with Sigstore: ```yaml - name: Sign Artifacts uses: sigstore/cosign-installer@main - run: | cosign sign-blob --key cosign.key artifact.tar.gz > artifact.tar.gz.sig ``` Access control for pipelines: - Protected branches (only certain users can merge/push) - Required reviews before pipeline runs - Branch protection rules - Environment-specific permissions GitHub: ```yaml environment: name: production required-reviewers: ['security-team', 'ops-lead'] ``` GitLab protected environments (Settings > CI/CD > Protected Environments). Audit logging: ```yaml - name: Log Deployment run: | echo "Deployed by ${{ github.actor }} at $(date)" >> audit.log curl -X POST https://audit.example.com/log \ -d '{"user":"${{ github.actor }}","action":"deploy"}' ``` Network security: - Private runners/agents behind firewall - Egress filtering (allow only necessary external access) - Secrets for webhook authentication - HTTPS for all communications Compliance as Code: ```yaml compliance_check: stage: validate script: - run-compliance-tests - check-license-compliance - verify-security-policies ``` Best practices: never commit secrets, use platform secret managers, integrate with Vault/AWS Secrets Manager for production, scan all dependencies, scan containers before deployment, implement SAST/DAST, generate and track SBOMs, sign artifacts, limit pipeline permissions, enable audit logging, regularly update dependencies, use automated security scanning in every pipeline. Understanding security practices prevents breaches and ensures compliance in CI/CD workflows.
Shared Library structure follows convention: vars/ contains global variables (pipeline functions), src/ contains Groovy classes (complex logic), resources/ contains non-Groovy files (templates, configs). Directory structure: ``` my-pipeline-library/ ├── vars/ │ ├── buildJava.groovy │ ├── deployApp.groovy │ └── sendNotification.groovy ├── src/ │ └── com/ │ └── example/ │ └── Utils.groovy └── resources/ └── templates/ └── Dockerfile.template ``` Global variables in vars/ define callable functions. Simple function (vars/buildJava.groovy): ```groovy def call(Map config) { pipeline { agent any stages { stage('Build') { steps { sh "mvn ${config.goals ?: 'clean package'}" } } stage('Test') { when { expression { config.runTests != false } } steps { sh 'mvn test' } } } } } ``` Usage in Jenkinsfile: ```groovy @Library('my-pipeline-library') _ buildJava(goals: 'clean install', runTests: true) ``` Custom steps (vars/deployApp.groovy): ```groovy def call(String environment) { echo "Deploying to ${environment}" sh "./deploy.sh ${environment}" } def rollback(String environment) { echo "Rolling back ${environment}" sh "./rollback.sh ${environment}" } ``` Usage: ```groovy @Library('my-pipeline-library') _ pipeline { stages { stage('Deploy') { steps { deployApp('production') } } } } ``` Groovy classes in src/ for complex logic (src/com/example/Utils.groovy): ```groovy package com.example class Utils implements Serializable { def script Utils(script) { this.script = script } def parseVersion(String tag) { def matcher = tag =~ /v(\d+)\.(\d+)\.(\d+)/ if (matcher) { return [major: matcher[0][1], minor: matcher[0][2], patch: matcher[0][3]] } return null } } ``` Usage: ```groovy @Library('my-pipeline-library') _ import com.example.Utils node { def utils = new Utils(this) def version = utils.parseVersion(env.TAG_NAME) echo "Version: ${version.major}.${version.minor}.${version.patch}" } ``` Library configuration: configure in Jenkins (Manage Jenkins > Configure System > Global Pipeline Libraries) with name, Git repository URL, and default version (branch/tag). Multiple libraries can be configured. Versioning: specify library version when loading: ```groovy @Library('my-pipeline-library@v1.2.3') _ // Specific tag @Library('my-pipeline-library@main') _ // Branch @Library('my-pipeline-library@commit') _ // Commit SHA @Library('my-pipeline-library') _ // Default version from config ``` Dynamic loading: ```groovy library identifier: 'my-lib@master', retriever: modernSCM([ $class: 'GitSCMSource', remote: 'https://github.com/org/pipeline-library.git' ]) ``` Resource files (resources/templates/Dockerfile.template): ```groovy def dockerfileTemplate = libraryResource 'templates/Dockerfile.template' writeFile file: 'Dockerfile', text: dockerfileTemplate ``` Best practices: version libraries (use semantic versioning), test library changes thoroughly before merging, document functions with comments and examples, keep libraries focused (separate libraries for different concerns), use classes for complex logic (easier to test), make functions configurable with sensible defaults, implement proper error handling, avoid side effects in library code, publish library documentation. Example testing library: ```groovy // test/vars/buildJavaTest.groovy import org.junit.Test import static org.junit.Assert.* class BuildJavaTest { @Test void testBuildJava() { // Mock script context and test function } } ``` Understanding Shared Libraries enables building reusable, maintainable pipeline code shared across organization, reducing duplication and standardizing CI/CD processes.
Try-catch blocks in script step or Scripted Pipeline handle exceptions: ```groovy stage('Build') { steps { script { try { sh 'mvn clean package' } catch (Exception e) { echo "Build failed: ${e.message}" currentBuild.result = 'FAILURE' throw e // Rethrow to fail pipeline } } } } ``` Caught exceptions can be logged, notifications sent, or pipeline behavior modified. Rethrowing fails the pipeline while catching without rethrowing continues. Error step explicitly fails pipeline with message: ```groovy if (!fileExists('required-file.txt')) { error 'Required file not found' } ``` Error stops execution immediately failing the stage and pipeline. CatchError wraps steps allowing pipeline to continue even if steps fail: ```groovy stage('Optional Task') { steps { catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') { sh 'npm run optional-task' // Pipeline continues even if this fails } } } ``` Parameters: buildResult (overall build result if error occurs), stageResult (stage result if error occurs), catchInterruptions (catch user abort). Useful for non-critical steps that shouldn't fail entire build. Post conditions handle cleanup and notifications based on result: ```groovy post { failure { echo 'Pipeline failed' mail to: 'team@example.com', subject: "Failed: ${env.JOB_NAME}", body: "${env.BUILD_URL}" } unstable { echo 'Tests failed but build succeeded' } always { junit '**/target/test-results/*.xml' cleanWs() } } ``` Always block runs regardless of result ensuring cleanup happens. Retry mechanism automatically retries failed steps: ```groovy stage('Flaky Test') { steps { retry(3) { sh 'npm run flaky-test' } } } ``` Retries up to 3 times before failing. Useful for flaky tests or transient network issues. Timeout prevents hanging builds: ```groovy stage('Long Running Task') { steps { timeout(time: 1, unit: 'HOURS') { sh './long-task.sh' } } } ``` Aborts step if exceeds timeout. Combining strategies: ```groovy stage('Deploy') { steps { script { def maxRetries = 3 def retryCount = 0 def success = false while (!success && retryCount < maxRetries) { try { timeout(time: 5, unit: 'MINUTES') { sh './deploy.sh' success = true } } catch (Exception e) { retryCount++ if (retryCount >= maxRetries) { error "Deployment failed after ${maxRetries} attempts: ${e.message}" } sleep(time: 30, unit: 'SECONDS') echo "Retry ${retryCount}/${maxRetries} after error: ${e.message}" } } } } } ``` CurrentBuild variable tracks build state: ```groovy script { currentBuild.result = 'UNSTABLE' // Set result currentBuild.description = 'Custom description' echo "Current result: ${currentBuild.result}" } ``` Results: SUCCESS, UNSTABLE, FAILURE, ABORTED. Best practices: use try-catch for expected errors requiring special handling, use catchError for optional steps, implement retry for transient failures, set timeouts preventing infinite hangs, use post conditions for cleanup ensuring it always runs, log errors with context for debugging, send notifications on failures, fail fast for critical errors, set meaningful build descriptions, avoid catching all exceptions unless necessary. Understanding error handling creates robust pipelines that gracefully handle failures and provide clear feedback.
Parameters make pipelines flexible by accepting user input at build time. Define parameters in parameters block (Declarative) or properties block (Scripted). Parameter types include string (text input), text (multi-line text), booleanParam (checkbox), choice (dropdown), password (masked input), file (file upload), and others. Declarative parameter definition: ```groovy pipeline { agent any parameters { string(name: 'VERSION', defaultValue: '1.0.0', description: 'Version to deploy') choice(name: 'ENVIRONMENT', choices: ['dev', 'staging', 'prod'], description: 'Target environment') booleanParam(name: 'RUN_TESTS', defaultValue: true, description: 'Run tests?') text(name: 'CHANGELOG', defaultValue: '', description: 'Changelog') } stages { stage('Deploy') { steps { echo "Deploying version ${params.VERSION} to ${params.ENVIRONMENT}" script { if (params.RUN_TESTS) { sh 'npm test' } } sh "./deploy.sh ${params.ENVIRONMENT} ${params.VERSION}" } } } } ``` Access parameters with params object: params.VERSION, params.ENVIRONMENT. First build uses default values, subsequent builds prompt user for input. Advanced parameters: ```groovy parameters { string( name: 'GIT_BRANCH', defaultValue: 'main', description: 'Branch to build', trim: true // Trim whitespace ) choice( name: 'BUILD_TYPE', choices: ['debug', 'release'], description: 'Build configuration' ) password( name: 'API_KEY', defaultValue: '', description: 'API Key for deployment' ) validatingString( name: 'EMAIL', defaultValue: 'user@example.com', regex: '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$', failedValidationMessage: 'Invalid email format', description: 'Notification email' ) } ``` Dynamic parameters using Active Choices plugin enable parameters depending on other parameters or external data. Example: environment choice determines available regions. Build with parameters: first run establishes parameter definitions, subsequent runs show "Build with Parameters" button. Trigger via API: ```bash curl -X POST http://jenkins/job/myjob/buildWithParameters \ --user user:token \ --data VERSION=1.2.0 \ --data ENVIRONMENT=production ``` Conditional logic based on parameters: ```groovy stage('Performance Tests') { when { expression { params.RUN_PERF_TESTS == true } } steps { sh 'npm run test:performance' } } ``` Multibranch pipeline parameters: Multibranch pipelines don't support parameters block (parameters would differ per branch). Use properties step: ```groovy properties([ parameters([ string(name: 'VERSION', defaultValue: '1.0.0') ]) ]) ``` Best practices: provide sensible defaults, add clear descriptions, validate input (regex, constraints), use choice for limited options preventing invalid input, avoid sensitive data in parameters (use credentials instead), document required parameters, limit parameter count (too many becomes confusing), use boolean for feature flags, consider parameter naming conventions. Understanding parameters enables building flexible, reusable pipelines accommodating different scenarios without code changes.
Caching reduces build time by reusing previous build artifacts and dependencies. Docker layer caching reuses unchanged layers: ```groovy stage('Build') { steps { script { docker.build( "myapp:${env.BUILD_NUMBER}", "--cache-from myapp:latest ." ) } } } ``` Dependency caching for Maven (.m2), npm (node_modules), pip (cache), etc. Use shared volumes or cache plugins: ```groovy stage('Install Dependencies') { steps { sh ''' if [ -d ~/.m2/repository ]; then cp -r ~/.m2/repository ./maven-cache fi mvn dependency:go-offline cp -r ~/.m2/repository ./maven-cache ''' } } ``` Or use Pipeline Caching plugin: ```groovy cache(maxCacheSize: 1000, caches: [ arbitraryFileCache(path: 'node_modules', cacheValidityDecidingFile: 'package-lock.json') ]) { sh 'npm install' } ``` Parallelization runs independent tasks concurrently: ```groovy stage('Test') { parallel { stage('Unit Tests') { steps { sh 'npm run test:unit' } } stage('Integration Tests') { steps { sh 'npm run test:integration' } } stage('E2E Tests') { steps { sh 'npm run test:e2e' } } } } ``` Ensures multiple agents available for parallel execution. Agent selection optimizes resource usage: ```groovy stage('Build') { agent { docker { image 'maven:3.8-jdk-11' args '-v /var/jenkins_home/maven-cache:/root/.m2' reuseNode true // Reuse workspace from parent } } steps { sh 'mvn package' } } ``` Use Docker agents for isolated environments, specific tools. Label-based selection: agent { label 'high-memory' } for resource-intensive tasks. Skip unnecessary work: ```groovy stage('Build') { when { changeset "src/**" } steps { sh 'make build' } } ``` Only builds when source code changes. Incremental builds avoid rebuilding unchanged code: ```groovy sh 'mvn -pl $(git diff --name-only HEAD~1 | grep pom.xml | xargs dirname) clean install' ``` Shallow clone reduces checkout time: ```groovy checkout([ $class: 'GitSCM', branches: [[name: "${env.BRANCH_NAME}"]], extensions: [[$class: 'CloneOption', depth: 1, noTags: true, shallow: true]], userRemoteConfigs: [[url: 'https://github.com/org/repo.git']] ]) ``` Workspace cleanup: ```groovy post { always { cleanWs() // Clean workspace after build } } ``` Prevents disk space issues but increases checkout time on next build. Balance based on workspace size and disk constraints. Reduce log verbosity: ```groovy sh 'mvn -q package' // Quiet mode ``` Large logs slow UI and consume storage. Timeout preventing hanging: ```groovy options { timeout(time: 1, unit: 'HOURS') } ``` Resource-intensive tasks on powerful agents: ```groovy stage('Performance Tests') { agent { label 'high-cpu' } steps { sh './run-perf-tests.sh' } } ``` Monitor and profile pipelines: - Analyze stage durations identifying bottlenecks - Review executor usage - Check agent queue times - Monitor disk I/O and network Best practices: cache dependencies aggressively, parallelize independent tasks, use appropriate agents (Docker for isolation, powerful agents for heavy tasks), skip unnecessary stages with when, implement shallow clones, clean workspaces periodically, set timeouts, reduce log verbosity, use incremental builds when possible, profile regularly identifying optimization opportunities. Understanding optimization techniques significantly reduces build times improving developer productivity.
Maven package phase executes the default lifecycle up to and including packaging. It compiles source code (compile phase), runs tests (test phase), and packages the compiled code into distributable format (JAR, WAR) defined in pom.xml. Package is commonly used in CI/CD pipelines for creating deployable artifacts. Maven build lifecycle consists of phases executed sequentially: validate, compile, test, package, verify, install, deploy. Running mvn package automatically executes all previous phases. Compile phase only compiles code without packaging. Install copies package to local repository (~/.m2/repository), deploy uploads to remote repository. Common Maven commands in CI/CD: mvn clean package (clean previous builds and package), mvn clean install (package and install to local repo), mvn clean deploy (package and deploy to remote repo), mvn verify (run integration tests). Understanding Maven lifecycle is essential for Java project automation.
Self-hosted runners are machines you manage that execute GitHub Actions workflows instead of using GitHub-hosted runners. Self-hosted runners provide control over hardware, software, network configuration, and can access internal resources. Use cases include specialized hardware requirements, proprietary software needs, faster build times with caching, or accessing on-premise resources. Setup involves installing runner application on your machine (Linux, Windows, macOS), registering with GitHub (organization, repository, or enterprise level), and configuring runner settings. Runners can be added to groups for access control and targeting. Jobs target self-hosted runners using runs-on: self-hosted or custom labels: runs-on: [self-hosted, linux, x64]. Advantages include control over environment (install any tools, custom configurations), access to internal resources (databases, APIs behind firewall), cost savings for heavy usage (no minute charges), persistent caching between builds, and specialized hardware (GPU, high memory). Disadvantages include maintenance overhead (updates, security patches), availability management, and security considerations (runners have access to repository secrets). Best practices for self-hosted runners: use dedicated machines (don't run on development workstations), implement automatic updates, monitor runner health, use runner groups for access control, consider ephemeral runners (fresh environment per job) for security, and implement proper network security. Understanding self-hosted runners enables optimizing workflow execution for specific organizational needs.
GitLab CI/CD YAML structure starts with defining stages (pipeline phases). Stages run sequentially, jobs within stages run in parallel. Default stages are build, test, deploy but custom stages can be defined: ```yaml stages: - build - test - security - deploy - cleanup ``` Jobs are tasks within stages. Job definition includes stage assignment, script (commands to run), and optional directives: ```yaml build_app: stage: build image: node:18 script: - npm install - npm run build artifacts: paths: - dist/ expire_in: 1 week cache: key: ${CI_COMMIT_REF_SLUG} paths: - node_modules/ only: - main - merge_requests ``` Artifacts are files generated by jobs passed to subsequent jobs or downloadable after pipeline completes. artifacts keyword specifies paths to preserve. Artifacts persist between stages allowing test stage to use build stage outputs. Example: build job creates dist/ directory, test job uses it: ```yaml test_app: stage: test dependencies: - build_app script: - npm test ``` Cache speeds up jobs by storing dependencies between pipeline runs. Unlike artifacts (passed between jobs in same pipeline), cache persists across pipelines. Cache key determines cache uniqueness, typically using branch name or lock file hash: ```yaml cache: key: files: - package-lock.json paths: - node_modules/ ``` Dependencies specify which jobs' artifacts this job needs. Without dependencies, job receives all previous stage artifacts. With dependencies: [job1, job2], only specified artifacts are available. Variables define environment variables: ```yaml variables: DATABASE_URL: "postgres://localhost/db" DEPLOY_ENV: "staging" job: script: - echo $DATABASE_URL ``` GitLab provides predefined variables (CI_COMMIT_SHA, CI_PIPELINE_ID, CI_COMMIT_BRANCH). Rules provide advanced conditional execution: ```yaml job: script: echo "Deploy" rules: - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' when: never - if: '$CI_COMMIT_BRANCH == "main"' when: on_success - when: manual ``` Before_script and after_script run before/after main script: ```yaml job: before_script: - echo "Setup" script: - echo "Main task" after_script: - echo "Cleanup" ``` Services provide additional containers for job (databases, caches): ```yaml test: image: node:18 services: - postgres:13 variables: POSTGRES_DB: testdb script: - npm test ``` Includes enable reusing configuration: ```yaml include: - local: '.gitlab-ci-templates.yml' - project: 'group/project' file: '/templates/ci.yml' - remote: 'https://example.com/ci-template.yml' ``` Extends inherits configuration from templates: ```yaml .deploy_template: script: - ./deploy.sh only: - main deploy_staging: extends: .deploy_template variables: ENVIRONMENT: staging deploy_prod: extends: .deploy_template variables: ENVIRONMENT: production ``` Triggers for multi-project pipelines: ```yaml trigger_downstream: stage: deploy trigger: project: group/downstream-project branch: main ``` Understanding GitLab CI/CD YAML syntax enables building sophisticated pipelines with proper dependency management, caching strategies, and modular configuration.
GitHub Actions workflow starts with name and trigger events: ```yaml name: CI/CD Pipeline on: push: branches: [ main, develop ] paths: - 'src/**' pull_request: branches: [ main ] schedule: - cron: '0 2 * * *' # Daily at 2 AM workflow_dispatch: # Manual trigger inputs: environment: description: 'Deployment environment' required: true default: 'staging' ``` Jobs define workflow tasks running in parallel by default. Jobs can have dependencies using needs: ```yaml jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - run: npm install - run: npm run build - uses: actions/upload-artifact@v3 with: name: dist path: dist/ test: needs: build runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/download-artifact@v3 with: name: dist - run: npm test deploy: needs: [build, test] runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' steps: - run: ./deploy.sh ``` Steps are individual tasks in job. Steps can run commands (run) or use actions (uses): ```yaml steps: - name: Checkout code uses: actions/checkout@v3 - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: '18' cache: 'npm' - name: Install dependencies run: npm ci - name: Run tests run: npm test env: NODE_ENV: test ``` Actions are reusable units from marketplace or custom. Actions have inputs (with) and outputs: ```yaml - uses: docker/build-push-action@v4 with: context: . push: true tags: ${{ secrets.DOCKER_USERNAME }}/myapp:latest cache-from: type=gha cache-to: type=gha,mode=max ``` Contexts provide information about workflow run: ```yaml steps: - name: Print context info run: | echo "Event: ${{ github.event_name }}" echo "Branch: ${{ github.ref }}" echo "Commit: ${{ github.sha }}" echo "Actor: ${{ github.actor }}" echo "Runner OS: ${{ runner.os }}" echo "Job status: ${{ job.status }}" ``` Secrets access sensitive data: ```yaml - name: Deploy run: ./deploy.sh env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} ``` Matrix strategy for multiple configurations: ```yaml strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] node: [14, 16, 18] fail-fast: false runs-on: ${{ matrix.os }} steps: - uses: actions/setup-node@v3 with: node-version: ${{ matrix.node }} ``` Conditionals control step/job execution: ```yaml steps: - name: Deploy to staging if: github.ref == 'refs/heads/develop' run: ./deploy-staging.sh - name: Deploy to production if: github.ref == 'refs/heads/main' && success() run: ./deploy-prod.sh ``` Environments for deployment protection: ```yaml jobs: deploy: runs-on: ubuntu-latest environment: name: production url: https://example.com steps: - run: ./deploy.sh ``` Environments can have required reviewers and wait timers. Reusable workflows enable workflow composition: ```yaml # .github/workflows/reusable-deploy.yml on: workflow_call: inputs: environment: required: true type: string secrets: deploy-key: required: true jobs: deploy: runs-on: ubuntu-latest steps: - run: ./deploy.sh ${{ inputs.environment }} env: DEPLOY_KEY: ${{ secrets.deploy-key }} # .github/workflows/main.yml jobs: deploy-staging: uses: ./.github/workflows/reusable-deploy.yml with: environment: staging secrets: deploy-key: ${{ secrets.STAGING_KEY }} ``` Understanding GitHub Actions syntax enables building sophisticated workflows with proper dependency management, reusability, and integration with GitHub ecosystem.
GitLab Runner is an application executing jobs from GitLab CI/CD. Runner architecture consists of runner manager (coordinates job execution), executor (environment where job runs), and job executor (specific implementation for platform). Runner polls GitLab instance for jobs matching its tags, downloads job artifacts, executes job script, uploads results and artifacts. Executor types determine how jobs run. Shell executor runs commands directly on runner machine, sharing environment with runner process. Simple but no isolation: ```toml [[runners]] name = "shell-runner" executor = "shell" ``` Use for simple cases or when runner has required tools installed. Docker executor runs each job in fresh Docker container providing isolation, clean environment per job, and easy tool management via Docker images: ```toml [[runners]] name = "docker-runner" executor = "docker" [runners.docker] image = "alpine:latest" privileged = false volumes = ["/cache", "/var/run/docker.sock:/var/run/docker.sock"] ``` Jobs specify image: ```yaml job: image: node:18 script: - npm test ``` Docker executor with DinD (Docker-in-Docker) enables building Docker images: ```yaml build_image: image: docker:20.10 services: - docker:20.10-dind variables: DOCKER_TLS_CERTDIR: "/certs" script: - docker build -t myapp . ``` Kubernetes executor runs jobs as Kubernetes pods, ideal for cloud-native environments: ```toml [[runners]] name = "k8s-runner" executor = "kubernetes" [runners.kubernetes] namespace = "gitlab-runner" image = "alpine:latest" privileged = false cpu_request = "100m" memory_request = "128Mi" service_cpu_request = "100m" service_memory_request = "128Mi" ``` Runner registration connects runner to GitLab: ```bash gitlab-runner register \ --non-interactive \ --url "https://gitlab.com/" \ --registration-token "PROJECT_REGISTRATION_TOKEN" \ --executor "docker" \ --docker-image "alpine:latest" \ --description "docker-runner" \ --tag-list "docker,linux" \ --locked="false" ``` Registration types: shared runners (available to all projects in GitLab instance), group runners (available to all projects in group), specific runners (tied to specific projects). Runners match jobs based on tags. Autoscaling with Docker Machine enables dynamic runner provisioning based on load: ```toml [[runners]] executor = "docker+machine" [runners.machine] IdleCount = 2 IdleTime = 600 MaxBuilds = 100 MachineDriver = "amazonec2" MachineName = "gitlab-runner-%s" MachineOptions = [ "amazonec2-access-key=...", "amazonec2-secret-key=...", "amazonec2-region=us-east-1", "amazonec2-instance-type=t3.medium" ] ``` Creates machines on demand when jobs queued, destroys after idle period. Kubernetes autoscaling uses Kubernetes cluster autoscaler. Deploy runner as Kubernetes deployment, cluster automatically scales nodes based on pod resource requests. Runner configuration file (config.toml) contains all runner settings: ```toml concurrent = 4 # Max concurrent jobs check_interval = 0 [[runners]] name = "production-runner" url = "https://gitlab.com/" token = "TOKEN" executor = "docker" [runners.cache] Type = "s3" Shared = true [runners.cache.s3] ServerAddress = "s3.amazonaws.com" BucketName = "runner-cache" ``` Cache configuration enables sharing cache between jobs: ```yaml variables: CACHE_KEY: "${CI_COMMIT_REF_SLUG}" cache: key: ${CACHE_KEY} paths: - node_modules/ policy: pull-push ``` Security considerations: don't use privileged mode unless necessary, isolate runners (dedicated machines/namespaces), use private Docker registry for base images, implement network policies restricting runner access, regularly update runners, rotate tokens, limit runner scope (specific > group > shared). Understanding runner architecture and configuration enables building scalable, secure CI/CD infrastructure.
GitHub Actions support three action types: JavaScript actions (run directly on runner), Docker container actions (run in container), and composite actions (combine multiple steps). JavaScript actions execute directly on runner (fast, no container overhead). Create action: 1. Create action repository with action.yml: ```yaml name: 'Hello World' description: 'Greet someone and record time' inputs: who-to-greet: description: 'Who to greet' required: true default: 'World' outputs: time: description: 'The time we greeted you' runs: using: 'node16' main: 'index.js' ``` 2. Create index.js: ```javascript const core = require('@actions/core'); const github = require('@actions/github'); try { const nameToGreet = core.getInput('who-to-greet'); console.log(`Hello ${nameToGreet}!`); const time = (new Date()).toTimeString(); core.setOutput('time', time); core.setSecret('mySecret'); // Mask in logs } catch (error) { core.setFailed(error.message); } ``` 3. Package dependencies: ```bash npm install @actions/core @actions/github npm install @vercel/ncc -g ncc build index.js -o dist ``` Usage: ```yaml steps: - uses: user/hello-world-action@v1 with: who-to-greet: 'GitHub' id: hello - run: echo "Time was ${{ steps.hello.outputs.time }}" ``` Docker container actions run in container with custom environment: 1. Create action.yml: ```yaml name: 'Container Action' description: 'Runs in Docker container' inputs: myInput: description: 'Input parameter' required: true runs: using: 'docker' image: 'Dockerfile' args: - ${{ inputs.myInput }} ``` 2. Create Dockerfile: ```dockerfile FROM alpine:3.15 COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh ENTRYPOINT ["/entrypoint.sh"] ``` 3. Create entrypoint.sh: ```bash #!/bin/sh -l echo "Input was $1" echo "::set-output name=result::Success" ``` Composite actions combine multiple steps (shell commands or other actions): ```yaml name: 'Composite Action' description: 'Runs multiple steps' inputs: node-version: description: 'Node.js version' required: true runs: using: 'composite' steps: - uses: actions/checkout@v3 shell: bash - uses: actions/setup-node@v3 with: node-version: ${{ inputs.node-version }} shell: bash - run: npm install shell: bash - run: npm test shell: bash ``` Composite actions simplify workflow by grouping common steps. Action inputs and outputs enable parameterization: ```yaml inputs: api-key: description: 'API key' required: true environment: description: 'Deployment environment' required: false default: 'staging' outputs: deployment-url: description: 'Deployed application URL' value: ${{ steps.deploy.outputs.url }} ``` Publishing to GitHub Marketplace: 1. Create release with tag (v1, v1.0.0) 2. Add action.yml metadata: ```yaml name: 'My Action' author: 'Your Name' description: 'Detailed description' branding: icon: 'activity' color: 'blue' ``` 3. Publish to marketplace through GitHub UI 4. Users reference: uses: username/action-name@v1 Versioning best practices: use semantic versioning, maintain major version tags (v1, v2), update major tag on releases (git tag -fa v1 -m "Update v1"), users can pin to specific version (v1.0.0) or major version (v1). Testing actions: create .github/workflows/test.yml: ```yaml on: [push] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: ./ # Test local action with: who-to-greet: 'Test' ``` Understanding custom actions enables creating reusable automation, sharing with community, and building organization-specific action libraries.
Semantic versioning (semver) is a versioning scheme using three-part version numbers: MAJOR.MINOR.PATCH (e.g., 2.3.1). MAJOR version increments for incompatible API changes (breaking changes), MINOR version increments for backwards-compatible new features, PATCH version increments for backwards-compatible bug fixes. Example: version 1.4.2 means major version 1, minor version 4 (4 feature releases since 1.0.0), patch 2 (2 bug fixes since 1.4.0). Incrementing to 2.0.0 indicates breaking changes, 1.5.0 adds new features, 1.4.3 fixes bugs. Pre-release versions use suffix: 1.0.0-alpha, 1.0.0-beta.1, 1.0.0-rc.1. CI/CD integration: automatically increment version based on commit messages (conventional commits), tag releases with version, use version in artifact names. Tools like semantic-release automate versioning based on commit history. Benefits include clear communication of change impact, predictable dependency management, automated release workflows. Understanding semver enables proper version management in software lifecycle.
Test parallelization runs tests concurrently reducing total execution time. Strategies: process-level parallelization (multiple test processes), pipeline-level parallelization (parallel CI jobs), test sharding (split tests across machines). Jest parallel execution (default): ```javascript // jest.config.js module.exports = { maxWorkers: '50%', // Use 50% of CPU cores // or maxWorkers: 4 // Fixed number of workers }; // Run sequentially if needed test.serial('sequential test', () => { // Test that must run alone }); ``` pytest parallel with pytest-xdist: ```bash # Install pip install pytest-xdist # Run with 4 workers pytest -n 4 # Auto-detect CPUs pytest -n auto # Load balancing pytest -n auto --dist loadscope ``` Test sharding splits tests across CI jobs: ```yaml # GitLab CI test: stage: test parallel: 4 script: - npm test -- --shard=$CI_NODE_INDEX/$CI_NODE_TOTAL ``` GitHub Actions matrix: ```yaml strategy: matrix: shard: [1, 2, 3, 4] steps: - run: npm test -- --shard=${{ matrix.shard }}/4 ``` Playwright sharding: ```javascript // playwright.config.js export default { workers: process.env.CI ? 4 : undefined, fullyParallel: true, }; // Run specific shard // npx playwright test --shard=1/4 ``` Intelligent test splitting based on duration: ```yaml # CircleCI with test splitting test: parallelism: 4 steps: - run: command: | circleci tests glob "tests/**/*.test.js" | \ circleci tests split --split-by=timings | \ xargs npm test ``` Database isolation for parallel tests: ```javascript // Create separate database per worker const workerId = process.env.JEST_WORKER_ID || '1'; const databaseName = `test_db_${workerId}`; beforeAll(async () => { await createDatabase(databaseName); await runMigrations(databaseName); }); afterAll(async () => { await dropDatabase(databaseName); }); ``` Testcontainers with port mapping: ```java @Container private static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:13") .withDatabaseName("testdb") // Random port avoids conflicts .withExposedPorts(5432); ``` Shared resource management with locks: ```javascript const lockfile = require('proper-lockfile'); test('test requiring exclusive resource', async () => { const release = await lockfile.lock('/tmp/test-resource.lock'); try { // Use shared resource exclusively await useSharedResource(); } finally { await release(); } }); ``` Test categorization for parallel execution: ```javascript // Fast tests (unit tests) describe('Unit Tests', () => { test('fast test 1', () => {}); test('fast test 2', () => {}); }); // Slow tests (integration tests) describe('Integration Tests', () => { test('slow test 1', async () => {}); test('slow test 2', async () => {}); }); ``` Pipeline with parallel stages: ```yaml stages: - test unit-tests: stage: test script: - npm run test:unit parallel: 2 integration-tests: stage: test script: - npm run test:integration parallel: 4 e2e-tests: stage: test script: - npm run test:e2e parallel: 8 ``` Dynamic test allocation: ```python # Split tests based on previous run times import json import sys with open('test-timings.json') as f: timings = json.load(f) tests = sorted(timings.items(), key=lambda x: x[1], reverse=True) shard = int(sys.argv[1]) # Current shard total_shards = int(sys.argv[2]) # Distribute tests evenly by duration shard_tests = tests[shard::total_shards] print(' '.join([t[0] for t in shard_tests])) ``` Handling test dependencies: ```javascript // Tests with dependencies run sequentially describe.serial('Order-dependent tests', () => { let userId; test('create user', async () => { userId = await createUser(); }); test('update user', async () => { await updateUser(userId); }); test('delete user', async () => { await deleteUser(userId); }); }); ``` Optimization strategies: 1. Profile test suite identifying slow tests 2. Run fast tests first (fail fast) 3. Balance shards by test duration 4. Use test-level parallelization for independent tests 5. Use job-level parallelization for test categories 6. Optimize slow tests before parallelizing 7. Monitor parallel execution efficiency Challenges and solutions: - Race conditions: isolate test data, use unique identifiers - Resource contention: use separate resources per worker - Flaky tests: fix root cause, don't just parallelize - Uneven shard duration: split by test duration not count - Setup overhead: cache dependencies, optimize startup Best practices: ensure test independence, isolate data, use random ports, implement proper cleanup, monitor execution times, balance shards intelligently, fail fast on critical tests. Understanding parallelization reduces feedback time from hours to minutes.
Code coverage measures percentage of code executed during test runs. Coverage types: line coverage (lines executed), branch coverage (if/else branches taken), function coverage (functions called), statement coverage (statements executed). Tools: JaCoCo (Java), Istanbul/nyc (JavaScript), Coverage.py (Python), SimpleCov (Ruby). Coverage reports show: total coverage percentage, uncovered lines/branches, coverage per file/package. Example: 85% line coverage means 85% of code lines executed by tests, 15% untested. Coverage identifies untested code requiring more tests. However, high coverage doesn't guarantee good tests - tests must assert correct behavior. CI/CD integration: generate coverage reports, enforce minimum thresholds (e.g., 80%), fail build if coverage drops, display coverage trends. Example: Codecov, Coveralls integrate with GitHub showing coverage changes in pull requests. Coverage helps but shouldn't be sole quality metric. Understanding code coverage enables measuring test completeness.
Continuous testing integrates testing throughout development lifecycle with automated execution and rapid feedback. Strategy components: test pyramid implementation, quality gates, shift-left testing, test automation at all levels. Test strategy by pipeline stage: ```yaml stages: - validate - unit-test - build - integration-test - security-scan - deploy-staging - acceptance-test - deploy-production # Stage 1: Pre-commit validation lint: stage: validate script: - npm run lint - npm run format-check only: - merge_requests # Stage 2: Unit tests (every commit) unit-tests: stage: unit-test script: - npm test -- --coverage coverage: '/Statements\s*:\s*(\d+\.\d+)%/' artifacts: reports: coverage_report: coverage_format: cobertura path: coverage/cobertura-coverage.xml rules: - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' - if: '$CI_COMMIT_BRANCH == "main"' # Stage 3: Build build: stage: build script: - docker build -t myapp:$CI_COMMIT_SHA . dependencies: - unit-tests # Stage 4: Integration tests integration-tests: stage: integration-test services: - postgres:13 - redis:6 script: - npm run test:integration only: - main - merge_requests # Stage 5: Security scanning security-scan: stage: security-scan script: - npm audit --audit-level=high - trivy image myapp:$CI_COMMIT_SHA - semgrep --config=auto src/ allow_failure: false # Stage 6: Deploy to staging deploy-staging: stage: deploy-staging script: - kubectl apply -f k8s/staging/ environment: name: staging only: - main # Stage 7: Acceptance/E2E tests acceptance-tests: stage: acceptance-test script: - npm run test:e2e:staging dependencies: - deploy-staging retry: max: 2 when: script_failure performance-tests: stage: acceptance-test script: - k6 run performance/load-test.js only: - main allow_failure: true # Stage 8: Production deployment deploy-production: stage: deploy-production script: - kubectl apply -f k8s/production/ environment: name: production when: manual only: - main # Smoke tests after production deploy smoke-tests: stage: deploy-production script: - npm run test:smoke:production dependencies: - deploy-production when: on_success ``` Quality gates enforce quality standards: ```javascript // quality-gates.js const qualityGates = { unitTests: { coverage: { statements: 80, branches: 75, functions: 80, lines: 80 }, passRate: 100 // All tests must pass }, staticAnalysis: { criticalIssues: 0, highIssues: 0, codeSmells: 50, duplication: 3 // percent }, security: { criticalVulnerabilities: 0, highVulnerabilities: 0, mediumVulnerabilities: 10 }, performance: { p95ResponseTime: 500, // ms errorRate: 1 // percent } }; function evaluateQualityGates(results) { const failures = []; // Check coverage if (results.coverage.statements < qualityGates.unitTests.coverage.statements) { failures.push(`Statement coverage ${results.coverage.statements}% below threshold`); } // Check vulnerabilities if (results.security.critical > qualityGates.security.criticalVulnerabilities) { failures.push(`${results.security.critical} critical vulnerabilities found`); } if (failures.length > 0) { console.error('Quality gate failed:'); failures.forEach(f => console.error(` - ${f}`)); process.exit(1); } console.log('All quality gates passed'); } ``` Test execution frequency: 1. On every commit: - Linting - Unit tests - Static analysis 2. On merge request: - All commit checks - Integration tests - Security scans - Code coverage 3. On main branch: - All MR checks - E2E tests (smoke suite) - Container scanning - Deploy to staging 4. Nightly: - Full E2E test suite - Performance tests - Cross-browser tests - Visual regression tests - Dependency updates check 5. Weekly: - Penetration testing - Chaos engineering tests - Disaster recovery drills Shift-left testing moves testing earlier: ```javascript // Pre-commit hooks with Husky // .husky/pre-commit #!/bin/sh . "$(dirname "$0")/_/husky.sh" npm run lint npm test -- --findRelatedTests --bail npm run type-check ``` Feedback mechanisms: 1. Pull request annotations: ```yaml test: script: - npm test -- --json --outputFile=test-results.json after_script: - node scripts/annotate-pr.js test-results.json ``` 2. Slack notifications: ```yaml notify: stage: .post script: - | curl -X POST $SLACK_WEBHOOK \ -d "{ 'text': 'Pipeline ${CI_PIPELINE_STATUS} for ${CI_COMMIT_REF_NAME}', 'attachments': [{ 'color': '${CI_PIPELINE_STATUS == "success" ? "good" : "danger"}', 'fields': [ {'title': 'Project', 'value': '${CI_PROJECT_NAME}'}, {'title': 'Branch', 'value': '${CI_COMMIT_REF_NAME}'}, {'title': 'Coverage', 'value': '${COVERAGE}%'} ] }] }" when: always ``` 3. Dashboard metrics: ```javascript // Collect test metrics const metrics = { timestamp: new Date(), pipeline: process.env.CI_PIPELINE_ID, branch: process.env.CI_COMMIT_BRANCH, unitTests: { total: testResults.numTotalTests, passed: testResults.numPassedTests, failed: testResults.numFailedTests, duration: testResults.testResults.reduce((sum, r) => sum + r.perfStats.end - r.perfStats.start, 0) }, coverage: { statements: coverage.total.statements.pct, branches: coverage.total.branches.pct } }; // Send to monitoring system await sendMetrics(metrics); ``` 4. Test trend analysis: ```python # Analyze test trends import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv('test-history.csv') df['date'] = pd.to_datetime(df['date']) # Plot coverage trend plt.plot(df['date'], df['coverage']) plt.axhline(y=80, color='r', linestyle='--', label='Threshold') plt.xlabel('Date') plt.ylabel('Coverage %') plt.title('Test Coverage Trend') plt.savefig('coverage-trend.png') ``` Best practices: 1. Fail fast (run fastest tests first) 2. Test in production-like environment 3. Automate everything 4. Make tests deterministic 5. Maintain test hygiene (remove obsolete tests) 6. Monitor test health metrics 7. Provide clear failure messages 8. Enable easy local reproduction 9. Balance speed and coverage 10. Continuous improvement (review metrics, optimize) Understanding continuous testing strategy ensures quality built into entire development process with rapid, automated feedback.
Unit testing frameworks vary by language but share common patterns. JUnit 5 (Java) provides annotations, assertions, and lifecycle management: ```java import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; class CalculatorTest { @Test void testAddition() { Calculator calc = new Calculator(); assertEquals(5, calc.add(2, 3)); } @Test void testDivisionByZero() { assertThrows(ArithmeticException.class, () -> new Calculator().divide(10, 0)); } } ``` Jest (JavaScript) offers zero-config setup, mocking, and snapshot testing: ```javascript test('adds 1 + 2 to equal 3', () => { expect(sum(1, 2)).toBe(3); }); test('user creation', async () => { const user = await createUser({ name: 'John' }); expect(user).toHaveProperty('id'); expect(user.name).toBe('John'); }); describe('Calculator', () => { beforeEach(() => { // Setup }); test('multiplication', () => { expect(multiply(2, 3)).toBe(6); }); }); ``` pytest (Python) uses simple assertions and fixtures: ```python import pytest def test_addition(): assert add(2, 3) == 5 def test_division_by_zero(): with pytest.raises(ZeroDivisionError): divide(10, 0) @pytest.mark.parametrize("input,expected", [ (2, 4), (3, 9), (4, 16) ]) def test_square(input, expected): assert square(input) == expected @pytest.fixture def database(): db = Database() yield db db.cleanup() ``` Integration testing uses Testcontainers for real dependencies: ```java @Container private static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:13"); @Test void testUserRepository() { UserRepository repo = new UserRepository(postgres.getJdbcUrl()); User user = repo.save(new User("John")); assertNotNull(user.getId()); } ``` E2E framework comparison: Selenium (mature, cross-browser): ```python from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC driver = webdriver.Chrome() driver.get("https://example.com/login") WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.ID, "username")) ) driver.find_element(By.ID, "username").send_keys("testuser") driver.find_element(By.ID, "password").send_keys("password123") driver.find_element(By.ID, "submit").click() assert "Dashboard" in driver.title driver.quit() ``` Pros: mature, multi-language, supports all browsers. Cons: verbose, requires WebDriver setup, slower, complex wait management. Cypress (modern, developer-friendly): ```javascript describe('User Login', () => { beforeEach(() => { cy.visit('/login'); }); it('should login successfully', () => { cy.get('[data-test="username"]').type('testuser'); cy.get('[data-test="password"]').type('password123'); cy.get('[data-test="submit"]').click(); cy.url().should('include', '/dashboard'); cy.get('h1').should('contain', 'Welcome'); }); it('should show error on invalid credentials', () => { cy.get('[data-test="username"]').type('invalid'); cy.get('[data-test="password"]').type('wrong'); cy.get('[data-test="submit"]').click(); cy.get('[data-test="error"]').should('be.visible'); }); }); ``` Pros: automatic waiting, time-travel debugging, network stubbing, great DX. Cons: limited cross-browser support initially (improving), runs in browser context. Playwright (newest, powerful): ```javascript const { test, expect } = require('@playwright/test'); test.describe('User Authentication', () => { test('successful login', async ({ page }) => { await page.goto('https://example.com/login'); await page.fill('[data-test="username"]', 'testuser'); await page.fill('[data-test="password"]', 'password123'); await page.click('[data-test="submit"]'); await expect(page).toHaveURL(/.*dashboard/); await expect(page.locator('h1')).toContainText('Welcome'); }); test('failed login', async ({ page }) => { await page.goto('https://example.com/login'); await page.fill('[data-test="username"]', 'invalid'); await page.fill('[data-test="password"]', 'wrong'); await page.click('[data-test="submit"]'); await expect(page.locator('[data-test="error"]')).toBeVisible(); }); test('mobile login', async ({ page }) => { await page.setViewportSize({ width: 375, height: 667 }); await page.goto('https://example.com/login'); // Test mobile-specific behavior }); }); ``` Pros: cross-browser (Chromium, Firefox, WebKit), auto-waiting, network interception, mobile emulation, parallel execution, trace viewer. Cons: newer ecosystem. Framework selection criteria: language alignment (Jest for Node.js, JUnit for Java), test type requirements, speed needs, cross-browser support, team expertise, ecosystem maturity. Understanding frameworks enables choosing appropriate tools for testing strategy.
Test-Driven Development (TDD) follows Red-Green-Refactor cycle. Red: write failing test defining expected behavior before implementation. Green: write minimal code passing the test. Refactor: improve code quality while keeping tests passing. TDD example: ```javascript // Red: Write failing test test('calculate total with discount', () => { const cart = new ShoppingCart(); cart.addItem({ price: 100, quantity: 2 }); expect(cart.getTotal(0.1)).toBe(180); // 10% discount }); // Green: Minimal implementation class ShoppingCart { constructor() { this.items = []; } addItem(item) { this.items.push(item); } getTotal(discount = 0) { const subtotal = this.items.reduce( (sum, item) => sum + (item.price * item.quantity), 0 ); return subtotal * (1 - discount); } } // Refactor: Add validation, error handling class ShoppingCart { constructor() { this.items = []; } addItem(item) { if (!item.price || item.price < 0) { throw new Error('Invalid price'); } if (!item.quantity || item.quantity < 1) { throw new Error('Invalid quantity'); } this.items.push(item); } getTotal(discount = 0) { if (discount < 0 || discount > 1) { throw new Error('Discount must be between 0 and 1'); } const subtotal = this.items.reduce( (sum, item) => sum + (item.price * item.quantity), 0 ); return subtotal * (1 - discount); } } // Add more tests for edge cases test('throws error on invalid discount', () => { const cart = new ShoppingCart(); cart.addItem({ price: 100, quantity: 1 }); expect(() => cart.getTotal(1.5)).toThrow('Discount must be between 0 and 1'); }); ``` TDD benefits: better design (tests drive API design), high coverage (tests written first), fewer bugs, confidence in refactoring, living documentation. Behavior-Driven Development (BDD) extends TDD using business-readable language (Gherkin): ```gherkin Feature: Shopping Cart As a customer I want to manage items in my cart So that I can purchase products Background: Given I am logged in as "customer@example.com" Scenario: Add item to cart Given I am on the product page for "iPhone 15" When I click "Add to Cart" Then I should see "1 item in cart" And the cart total should be "$999" Scenario: Apply discount code Given I have 1 "iPhone 15" in my cart When I apply discount code "SAVE10" Then the cart total should be "$899.10" And I should see "Discount applied: 10%" Scenario Outline: Bulk purchase discount Given I have <quantity> "iPhone 15" in my cart When I proceed to checkout Then I should receive <discount>% discount And the total should be <total> Examples: | quantity | discount | total | | 1 | 0 | $999.00 | | 5 | 5 | $4745.25 | | 10 | 10 | $8991.00 | ``` Step definitions implement scenarios: ```javascript const { Given, When, Then } = require('@cucumber/cucumber'); const { expect } = require('chai'); Given('I am logged in as {string}', async function (email) { this.user = await loginUser(email); }); Given('I am on the product page for {string}', async function (product) { await this.page.goto(`/products/${product}`); }); When('I click {string}', async function (buttonText) { await this.page.click(`button:has-text("${buttonText}")`); }); Then('I should see {string}', async function (text) { await expect(this.page.locator('body')).toContainText(text); }); Then('the cart total should be {string}', async function (amount) { const total = await this.page.locator('[data-test="cart-total"]').textContent(); expect(total).to.equal(amount); }); When('I apply discount code {string}', async function (code) { await this.page.fill('[data-test="discount-code"]', code); await this.page.click('[data-test="apply-discount"]'); }); ``` CI/CD integration: ```yaml # GitLab CI stages: - unit-test - integration-test - bdd-test unit-tests: stage: unit-test script: - npm test coverage: '/Statements\s*:\s*(\d+\.\d+)%/' rules: - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' - if: '$CI_COMMIT_BRANCH == "main"' integration-tests: stage: integration-test services: - postgres:13 script: - npm run test:integration only: - main bdd-tests: stage: bdd-test script: - npm run cucumber artifacts: when: always reports: junit: reports/cucumber-report.xml paths: - reports/ only: - main - /^release/.*$/ ``` GitHub Actions: ```yaml name: TDD/BDD Pipeline on: [push, pull_request] jobs: unit-tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: '18' cache: 'npm' - run: npm ci - run: npm test - uses: codecov/codecov-action@v3 bdd-tests: runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' steps: - uses: actions/checkout@v3 - run: npm ci - run: npm run cucumber - uses: actions/upload-artifact@v3 if: always() with: name: cucumber-report path: reports/ ``` Best practices: write tests first (TDD discipline), keep tests fast, make tests deterministic, use descriptive names, test behaviors not implementation, refactor tests regularly, run TDD unit tests on every commit, run BDD acceptance tests on main branch or before release, enforce coverage thresholds, review tests in pull requests. Understanding TDD/BDD integrates quality into development process from start.
Performance testing validates application speed, scalability, and stability under load. Types: load testing (normal expected load), stress testing (beyond normal capacity), spike testing (sudden traffic increase), endurance testing (sustained load over time). Apache JMeter (GUI and CLI): ```xml <!-- Create test plan programmatically --> <jmeterTestPlan version="1.2"> <hashTree> <TestPlan guiclass="TestPlanGui" testclass="TestPlan"> <elementProp name="TestPlan.user_defined_variables"> <collectionProp name="Arguments.arguments"> <elementProp name="USERS" elementType="Argument"> <stringProp name="Argument.value">100</stringProp> </elementProp> </collectionProp> </elementProp> </TestPlan> <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup"> <stringProp name="ThreadGroup.num_threads">${USERS}</stringProp> <stringProp name="ThreadGroup.ramp_time">10</stringProp> <stringProp name="ThreadGroup.duration">60</stringProp> </ThreadGroup> </hashTree> </jmeterTestPlan> ``` Run in CI/CD: ```yaml performance_test: stage: test script: - jmeter -n -t test-plan.jmx -l results.jtl -j jmeter.log - jmeter-plugins-cmd --generate-png report.png --input-jtl results.jtl artifacts: paths: - results.jtl - report.png only: - main ``` K6 (modern, JavaScript-based): ```javascript import http from 'k6/http'; import { check, sleep } from 'k6'; import { Rate } from 'k6/metrics'; const errorRate = new Rate('errors'); export const options = { stages: [ { duration: '1m', target: 50 }, // Ramp up { duration: '3m', target: 100 }, // Stay at 100 { duration: '1m', target: 0 }, // Ramp down ], thresholds: { http_req_duration: ['p(95)<500'], // 95% requests < 500ms http_req_failed: ['rate<0.01'], // <1% error rate errors: ['rate<0.1'], }, }; export default function () { const res = http.get('https://api.example.com/products'); const success = check(res, { 'status is 200': (r) => r.status === 200, 'response time < 500ms': (r) => r.timings.duration < 500, }); errorRate.add(!success); sleep(1); } ``` CI/CD integration: ```yaml k6_test: stage: performance image: grafana/k6:latest script: - k6 run --out json=results.json test.js - k6 run --out influxdb=http://influxdb:8086 test.js artifacts: paths: - results.json only: - main ``` Gatling (Scala-based, for JVM apps): ```scala import io.gatling.core.Predef._ import io.gatling.http.Predef._ import scala.concurrent.duration._ class BasicSimulation extends Simulation { val httpProtocol = http .baseUrl("https://api.example.com") .acceptHeader("application/json") val scn = scenario("API Load Test") .exec( http("Get Products") .get("/products") .check(status.is(200)) .check(responseTimeInMillis.lte(500)) ) .pause(1) setUp( scn.inject( rampUsers(100) during (60 seconds) ) ).protocols(httpProtocol) .assertions( global.responseTime.max.lt(1000), global.successfulRequests.percent.gt(95) ) } ``` Baseline performance establishes reference metrics: ```yaml baseline_test: stage: performance script: - k6 run --vus 10 --duration 5m baseline.js - python compare_with_baseline.py results.json baseline.json only: - main ``` Performance regression detection: ```python # compare_with_baseline.py import json import sys with open('results.json') as f: current = json.load(f) with open('baseline.json') as f: baseline = json.load(f) current_p95 = current['metrics']['http_req_duration']['p(95)'] baseline_p95 = baseline['metrics']['http_req_duration']['p(95)'] regression_threshold = 0.1 # 10% slower if current_p95 > baseline_p95 * (1 + regression_threshold): print(f"Performance regression detected: {current_p95}ms vs {baseline_p95}ms") sys.exit(1) else: print(f"Performance acceptable: {current_p95}ms vs {baseline_p95}ms") ``` Scalability testing: ```javascript // K6 scalability test export const options = { scenarios: { low_load: { executor: 'constant-vus', vus: 10, duration: '5m', startTime: '0s', }, medium_load: { executor: 'ramping-vus', startVUs: 10, stages: [ { duration: '2m', target: 50 }, { duration: '5m', target: 50 }, ], startTime: '5m', }, high_load: { executor: 'ramping-vus', startVUs: 50, stages: [ { duration: '2m', target: 100 }, { duration: '5m', target: 100 }, { duration: '2m', target: 0 }, ], startTime: '12m', }, }, }; ``` Monitoring during tests: ```yaml performance_test: stage: performance services: - name: grafana/grafana:latest - name: influxdb:latest script: - k6 run --out influxdb=http://influxdb:8086 test.js - curl -X POST http://grafana:3000/api/annotations \ -d '{"text":"Performance test completed"}' ``` Best practices: establish baseline early, run performance tests regularly (nightly or weekly), test realistic scenarios, monitor system resources (CPU, memory, database), test against production-like environment, use proper test data, include think time (user pauses), gradually increase load, fail builds on significant regressions, track metrics over time. Understanding performance testing prevents scalability issues in production.
Test data management ensures tests have consistent, realistic data without exposing sensitive information. Strategies: synthetic data generation, database seeding, test fixtures, data masking. Synthetic data generation creates realistic test data: ```javascript // Faker.js for realistic data const { faker } = require('@faker-js/faker'); function generateTestUser() { return { id: faker.string.uuid(), email: faker.internet.email(), firstName: faker.person.firstName(), lastName: faker.person.lastName(), phone: faker.phone.number(), address: { street: faker.location.streetAddress(), city: faker.location.city(), zipCode: faker.location.zipCode(), }, createdAt: faker.date.past(), }; } const testUsers = Array.from({ length: 100 }, generateTestUser); ``` Test fixtures provide consistent test data: ```python # pytest fixtures import pytest @pytest.fixture def test_user(): return { 'id': 1, 'email': 'test@example.com', 'name': 'Test User' } @pytest.fixture def database(tmp_path): db_path = tmp_path / "test.db" db = Database(db_path) db.create_tables() yield db db.close() def test_create_user(database, test_user): user_id = database.create_user(test_user) assert user_id == test_user['id'] ``` Database seeding for integration tests: ```javascript // seeds/test-data.js exports.seed = async function(knex) { await knex('users').del(); await knex('products').del(); await knex('users').insert([ { id: 1, email: 'user1@example.com', role: 'customer' }, { id: 2, email: 'admin@example.com', role: 'admin' }, ]); await knex('products').insert([ { id: 1, name: 'Product 1', price: 99.99, stock: 100 }, { id: 2, name: 'Product 2', price: 149.99, stock: 50 }, ]); }; // Run in tests beforeEach(async () => { await knex.migrate.latest(); await knex.seed.run(); }); afterEach(async () => { await knex.migrate.rollback(); }); ``` Testcontainers with seeded database: ```java @Container private static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:13") .withDatabaseName("testdb") .withInitScript("test-data.sql"); @BeforeEach void setUp() { DataSource dataSource = createDataSource(postgres.getJdbcUrl()); userRepository = new UserRepository(dataSource); } ``` test-data.sql: ```sql INSERT INTO users (id, email, name) VALUES (1, 'test1@example.com', 'Test User 1'), (2, 'test2@example.com', 'Test User 2'); INSERT INTO orders (id, user_id, total, status) VALUES (1, 1, 99.99, 'completed'), (2, 1, 149.99, 'pending'); ``` Data masking for production data: ```python # Mask sensitive data from production backup import hashlib def mask_email(email): username, domain = email.split('@') return f"{username[:2]}***@{domain}" def mask_phone(phone): return f"***-***-{phone[-4:]}" def anonymize_user(user): return { **user, 'email': mask_email(user['email']), 'phone': mask_phone(user['phone']), 'ssn': hashlib.sha256(user['ssn'].encode()).hexdigest()[:10], 'credit_card': None, } # Apply to production data export with open('production_users.json') as f: users = json.load(f) masked_users = [anonymize_user(u) for u in users] with open('test_users.json', 'w') as f: json.dump(masked_users, f) ``` CI/CD data management: ```yaml # GitLab CI test: stage: test services: - postgres:13 variables: DATABASE_URL: "postgresql://test:test@postgres:5432/testdb" before_script: - npm run db:migrate - npm run db:seed script: - npm test after_script: - npm run db:reset ``` Factory pattern for test objects: ```javascript // factories/user.factory.js class UserFactory { static build(overrides = {}) { return { id: faker.string.uuid(), email: faker.internet.email(), name: faker.person.fullName(), role: 'customer', createdAt: new Date(), ...overrides }; } static buildAdmin() { return this.build({ role: 'admin' }); } static async create(overrides = {}) { const user = this.build(overrides); return await database.users.create(user); } } // Usage in tests test('admin can delete users', async () => { const admin = await UserFactory.create({ role: 'admin' }); const user = await UserFactory.create(); const result = await deleteUser(admin, user.id); expect(result).toBe(true); }); ``` Data privacy considerations: 1. Never use real production data directly 2. Mask PII (personally identifiable information) 3. Use synthetic data for tests 4. Secure test data storage 5. Rotate test credentials regularly 6. Document data classification 7. Comply with GDPR, CCPA requirements Best practices: use factories for object creation, seed minimal required data, clean up after tests, isolate test data per test, version control seed files, automate data generation, use realistic but fake data, implement data refresh strategies. Understanding test data management ensures reliable, privacy-compliant tests.
Environment strategy defines progression from development to production ensuring quality while managing complexity. Standard environments: local (developer machine), development (shared dev environment), staging (pre-production), production (live users). Optional: QA (dedicated testing), UAT (user acceptance), integration (service integration testing), performance (load testing), canary (progressive rollout). Environment characteristics: Local development: ```yaml # docker-compose.yml for local version: '3.8' services: app: build: . environment: - NODE_ENV=development - DATABASE_URL=postgresql://localhost:5432/dev - REDIS_URL=redis://localhost:6379 - LOG_LEVEL=debug volumes: - ./src:/app/src # Hot reload ports: - "3000:3000" postgres: image: postgres:13 environment: - POSTGRES_DB=dev - POSTGRES_PASSWORD=dev volumes: - postgres-data:/var/lib/postgresql/data redis: image: redis:6 ``` Development environment: ```yaml # k8s/dev/deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: myapp namespace: development spec: replicas: 1 template: spec: containers: - name: myapp image: myapp:latest env: - name: ENVIRONMENT value: "development" - name: DATABASE_URL valueFrom: secretKeyRef: name: db-credentials key: url resources: requests: memory: "256Mi" cpu: "250m" limits: memory: "512Mi" cpu: "500m" ``` Staging environment (production-like): ```yaml # k8s/staging/deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: myapp namespace: staging spec: replicas: 2 # Similar to production template: spec: containers: - name: myapp image: myapp:v1.2.3 # Tagged version env: - name: ENVIRONMENT value: "staging" resources: requests: memory: "512Mi" cpu: "500m" limits: memory: "1Gi" cpu: "1000m" affinity: # Similar to production podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchLabels: app: myapp topologyKey: kubernetes.io/hostname ``` Production environment: ```yaml # k8s/production/deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: myapp namespace: production spec: replicas: 5 strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0 template: spec: containers: - name: myapp image: myapp:v1.2.3 env: - name: ENVIRONMENT value: "production" livenessProbe: httpGet: path: /health port: 8080 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /ready port: 8080 initialDelaySeconds: 5 periodSeconds: 5 resources: requests: memory: "512Mi" cpu: "500m" limits: memory: "1Gi" cpu: "1000m" ``` Configuration management per environment: ```javascript // config/index.js const configs = { development: { api: { baseUrl: 'http://localhost:3000', timeout: 30000 }, features: { newCheckout: true, analytics: false }, database: { pool: { min: 2, max: 10 } } }, staging: { api: { baseUrl: 'https://api-staging.example.com', timeout: 10000 }, features: { newCheckout: true, analytics: true }, database: { pool: { min: 5, max: 20 } } }, production: { api: { baseUrl: 'https://api.example.com', timeout: 5000 }, features: { newCheckout: false, analytics: true }, database: { pool: { min: 10, max: 50 } } } }; module.exports = configs[process.env.NODE_ENV || 'development']; ``` Secrets management: ```yaml # Using Sealed Secrets (encrypted in Git) apiVersion: bitnami.com/v1alpha1 kind: SealedSecret metadata: name: db-credentials namespace: production spec: encryptedData: url: AgBH7Vw8K... # Encrypted value --- # Using External Secrets Operator (from Vault) apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: db-credentials spec: refreshInterval: 1h secretStoreRef: name: vault-backend kind: SecretStore target: name: db-credentials data: - secretKey: url remoteRef: key: database/production property: connection_url ``` Environment parity checklist: 1. Same infrastructure components (load balancer, database, cache) 2. Similar resource allocation (staging = 50% of production) 3. Same configuration structure (different values) 4. Same monitoring and logging 5. Same deployment process 6. Realistic data volumes in staging Terraform workspaces for environment management: ```hcl # main.tf terraform { backend "s3" { bucket = "terraform-state" key = "myapp/terraform.tfstate" region = "us-east-1" } } variable "environment" { type = string } locals { config = { dev = { instance_count = 1 instance_type = "t3.small" db_instance = "db.t3.micro" } staging = { instance_count = 2 instance_type = "t3.medium" db_instance = "db.t3.small" } production = { instance_count = 5 instance_type = "t3.large" db_instance = "db.r5.large" } } env_config = local.config[var.environment] } resource "aws_instance" "app" { count = local.env_config.instance_count instance_type = local.env_config.instance_type tags = { Environment = var.environment } } ``` CI/CD pipeline with environment progression: ```yaml stages: - build - deploy-dev - test-dev - deploy-staging - test-staging - deploy-production deploy-dev: stage: deploy-dev script: - ./deploy.sh dev $CI_COMMIT_SHA environment: name: development url: https://dev.example.com only: - branches test-dev: stage: test-dev script: - npm run test:integration -- --env=dev deploy-staging: stage: deploy-staging script: - ./deploy.sh staging $CI_COMMIT_SHA environment: name: staging url: https://staging.example.com only: - main test-staging: stage: test-staging script: - npm run test:e2e -- --env=staging - npm run test:performance -- --env=staging deploy-production: stage: deploy-production script: - ./deploy.sh production $CI_COMMIT_TAG environment: name: production url: https://example.com when: manual only: - tags ``` Best practices: maintain environment parity (staging mirrors production), automate environment provisioning (Infrastructure as Code), isolate environments (separate networks/accounts), use consistent naming, implement proper access control, document environment differences, use environment-specific configurations, test production deployment in staging first, maintain separate credentials per environment, implement cost controls (auto-shutdown dev environments), regular environment refresh (reset staging data). Understanding environment management ensures quality progression to production.
Think of it like infrastructure as code, but for delivery. You commit the pipeline file, review it, and track its history. This gives repeatability, easy rollbacks, and the same process across branches and teams.
Jenkinsfile or .github/workflows/ci.yml stored at repo root
Conditions like when, rules, or if statements let you run jobs only for release events. That keeps regular pushes fast and safe.
if: startsWith(github.ref, 'refs/tags/') steps: - run: ./publish.sh
Jenkins is an open-source automation server written in Java that helps automate parts of software development related to building, testing, and deploying, facilitating continuous integration and continuous delivery (CI/CD). It was originally called Hudson and was renamed to Jenkins in 2011 after a dispute with Oracle. Jenkins supports building, deploying and automating software development projects through pipelines. It integrates with version control systems like Git, build tools like Maven and Gradle, testing frameworks, and deployment platforms. Jenkins has a vast ecosystem with over 1,500 plugins extending its functionality for various tools and technologies. Key features include distributed builds across multiple machines, extensive plugin ecosystem, pipeline as code with Jenkinsfile, web-based GUI for configuration, REST API for automation, and support for various SCM tools. Jenkins is widely adopted by organizations of all sizes from startups to enterprises for automating their software delivery processes.
Freestyle projects are the traditional Jenkins job type configured entirely through the web UI using point-and-click interface. Configuration is stored in Jenkins master's XML files and not version controlled. Freestyle projects are simple for basic tasks but become difficult to maintain for complex workflows, don't support sophisticated flow control, and configuration can't be reviewed or versioned alongside application code. Pipeline projects define the entire build process as code in a Jenkinsfile stored in version control with the application code (Pipeline as Code). Pipelines support complex workflows with stages, parallel execution, conditional logic, error handling, and can be version controlled, reviewed through pull requests, and tested like application code. Pipelines are more powerful and maintainable for complex CI/CD workflows. Pipeline advantages include version control of build configuration, code review for pipeline changes, ability to resume pipelines after Jenkins restarts, parallel execution support, better visualization with Blue Ocean, and treating infrastructure as code. Modern best practice is using Pipelines over Freestyle projects for all but the simplest jobs. Understanding both types helps maintain legacy systems while building new pipelines properly.
Jenkins Credentials plugin provides centralized, secure storage for secrets including passwords, API tokens, SSH keys, certificates, and secret files. Credentials are encrypted on master and stored securely, never exposed in logs or console output. Each credential has a unique ID used to reference it in jobs and pipelines without revealing the actual secret. Credential types include Username with password (for basic auth), Secret text (API tokens, passwords), Secret file (configuration files with secrets), SSH Username with private key (for Git/SSH access), Certificate, and Docker server credentials. Credentials have scope (System-wide available to Jenkins, or Global available to jobs) and can be restricted to specific folders or jobs. Usage in pipelines: withCredentials block provides temporary access to credentials as environment variables. Example: withCredentials([usernamePassword(credentialsId: 'github-creds', usernameVariable: 'USER', passwordVariable: 'PASS')]) { sh 'git push https://$USER:$PASS@github.com/repo.git' }. Use credential IDs, never hardcode secrets in Jenkinsfile. Integration with external secret management systems (HashiCorp Vault, AWS Secrets Manager) available through plugins. Understanding secure credential management prevents secret exposure.
Jenkins master is the central server managing the entire Jenkins environment. Master responsibilities include hosting the Jenkins UI (web interface), storing configuration (jobs, pipelines, system settings), scheduling builds and distributing to agents, monitoring agent health, collecting and displaying build results, managing plugins, and handling authentication/authorization. Master runs on JVM and stores data in JENKINS_HOME directory containing jobs, build history, plugins, and configuration. Agents (nodes) are worker machines executing builds. Agent types include permanent agents (always connected machines), cloud agents (dynamically provisioned on demand in AWS, Azure, GCP, Kubernetes), and Docker agents (ephemeral containers for isolated builds). Agents require Java runtime, can run on various OS (Linux, Windows, macOS), and connect to master via SSH, JNLP, or as controller-initiated connections. Communication between master and agents uses TCP/IP with authentication and optional encryption. Master sends build jobs to agents with workspace and environment details. Agents execute builds, stream console output back to master, and report results. Agents can have labels (tags like 'linux', 'docker', 'python3') allowing pipelines to target specific agent types: agent { label 'docker' } runs on agents with 'docker' label. Scaling strategies include horizontal scaling by adding more agents for parallel builds, vertical scaling by increasing agent resources (CPU, RAM), using cloud-based dynamic agents that scale automatically based on build queue depth, Kubernetes plugin for auto-scaling agent pods, and Docker plugin for on-demand container agents. Master should not execute builds in production to remain responsive and avoid resource contention. Best practices: separate master and agents (never run builds on master in production), use labels for agent targeting, implement master high availability with active-passive setup or clustering plugins, backup JENKINS_HOME regularly, use Configuration as Code plugin for master configuration, monitor agent connectivity and health, set executor count on master to 0, use cloud agents for scaling, implement resource limits on agents, and secure communication with SSL/TLS. Understanding architecture enables building robust, scalable Jenkins deployments.
Basic parallel execution runs independent stages concurrently reducing total pipeline time. Example: ```groovy stage('Tests') { parallel { stage('Unit Tests') { steps { sh 'npm test' } } stage('Integration Tests') { steps { sh 'npm run test:integration' } } stage('Lint') { steps { sh 'npm run lint' } } } } ``` FailFast option aborts all parallel stages if any fails, useful when one failure makes others irrelevant: ```groovy stage('Tests') { failFast true parallel { stage('Unit Tests') { steps { sh 'npm test' } } stage('Integration Tests') { steps { sh 'npm run test:integration' } } } } ``` Without failFast, all parallel stages run to completion even if some fail. Nested parallel stages enable complex concurrency patterns: ```groovy stage('Build and Test') { parallel { stage('Backend') { stages { stage('Backend Build') { steps { sh 'mvn package' } } stage('Backend Test') { steps { sh 'mvn test' } } } } stage('Frontend') { stages { stage('Frontend Build') { steps { sh 'npm run build' } } stage('Frontend Test') { steps { sh 'npm test' } } } } } } ``` Backend and Frontend run in parallel, each with sequential internal stages. Matrix builds (Jenkins 2.22+) run same stage with different parameter combinations: ```groovy stage('Test') { matrix { axes { axis { name 'PLATFORM' values 'linux', 'windows', 'mac' } axis { name 'BROWSER' values 'chrome', 'firefox', 'safari' } } stages { stage('Test') { steps { sh "./test.sh ${PLATFORM} ${BROWSER}" } } } } } ``` Creates 9 parallel executions testing all platform-browser combinations. Resource management considerations: each parallel stage requires agent/executor, so parallel stages = required executors. If insufficient executors available, stages queue. Use agent labels for specific requirements: ```groovy parallel { stage('Linux Build') { agent { label 'linux' } steps { sh 'make' } } stage('Windows Build') { agent { label 'windows' } steps { bat 'build.bat' } } } ``` Shared resources (databases, external services) may have concurrency limits. Use lock step to synchronize access: ```groovy parallel { stage('Test Suite 1') { steps { lock(resource: 'test-db', quantity: 1) { sh 'npm run test:suite1' } } } stage('Test Suite 2') { steps { lock(resource: 'test-db', quantity: 1) { sh 'npm run test:suite2' } } } } ``` Ensures only one stage accesses test-db at a time. Best practices: use parallel for independent tasks only (no shared state), set timeouts to prevent hanging, use failFast when appropriate, monitor executor utilization (avoid over-parallelization), consider cost (cloud agents charge per executor), implement resource locks for shared resources, use matrix for combinatorial testing. Understanding advanced parallel patterns optimizes pipeline performance while managing resources effectively.
GitLab Runners are agents that execute jobs defined in .gitlab-ci.yml. Runners can be shared (available to all projects), group (available to group projects), or specific (dedicated to particular project). Runners run on various platforms (Linux, Windows, macOS, Docker, Kubernetes) and can be hosted by GitLab.com (shared runners) or self-hosted by organizations. Runner types include Shell executor (runs commands directly on runner machine), Docker executor (runs jobs in Docker containers for isolation), Kubernetes executor (runs jobs as Kubernetes pods), and VirtualBox/Parallels executors (runs jobs in VMs). Docker executor is most popular providing clean, isolated environments for each job. Runner registration connects runner to GitLab instance using registration token from GitLab settings. After registration, runner polls GitLab for jobs matching its tags. Jobs can specify required tags targeting specific runners: tags: ['docker', 'linux']. Multiple runners provide parallel job execution and high availability. Understanding runners is essential for GitLab CI/CD execution infrastructure.
GitHub Actions Marketplace is a central hub for discovering and sharing reusable actions. Actions are pre-built steps that can be included in workflows, eliminating need to write common tasks from scratch. Marketplace contains thousands of actions for tasks like checking out code, setting up programming languages, deploying to cloud providers, sending notifications, and more. Actions are referenced using uses keyword with owner/repo@version syntax: uses: actions/checkout@v3 uses checkout action from GitHub's actions organization at version 3. Marketplace actions are categorized (CI, deployment, code quality, security) and searchable. Popular actions include actions/checkout (checkout repository), actions/setup-node (setup Node.js), docker/build-push-action (build Docker images), and aws-actions/configure-aws-credentials (AWS authentication). Using marketplace actions: ```yaml steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: '18' - uses: docker/build-push-action@v4 with: push: true tags: myapp:latest ``` Marketplace accelerates workflow development by providing tested, maintained actions. You can also publish your own actions to marketplace. Understanding marketplace enables leveraging community contributions and avoiding reinventing common tasks.
Multi-stage builds create optimized, secure Docker images by separating build and runtime dependencies. Build stage includes compilers and build tools, final stage only contains runtime requirements and artifacts. This reduces image size and attack surface. Example multi-stage Dockerfile: ```dockerfile # Build stage FROM node:18 AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build # Production stage FROM node:18-alpine WORKDIR /app COPY --from=builder /app/dist ./dist COPY package*.json ./ RUN npm ci --only=production USER node CMD ["node", "dist/index.js"] ``` CI/CD best practices: use layer caching (order Dockerfile from least to most frequently changing), tag images with commit SHA or version, scan for vulnerabilities (Trivy, Snyk), use BuildKit for faster builds (DOCKER_BUILDKIT=1), push to registry after successful build. Security: run as non-root user, use minimal base images (alpine), scan for vulnerabilities. Understanding Docker build optimization is crucial for efficient, secure CI/CD pipelines.
Incremental compilation compiles only modified source files and their dependencies instead of recompiling entire codebase. Build tools track file changes using timestamps or content hashes, identifying which files need recompilation. This dramatically reduces build time for large projects where typically only small percentage of code changes between builds. Gradle incremental compilation tracks input/output files: ```groovy tasks.withType(JavaCompile) { options.incremental = true } ``` Maven uses incremental flag: ```bash mvn compile -Dmaven.compiler.incremental=true ``` TypeScript/JavaScript bundlers (webpack, esbuild) use incremental builds by default, caching compiled modules. Factors affecting incremental builds: file dependency graphs (changing core files triggers more recompilation), cache management, build tool configuration. Combined with dependency caching and parallel execution, incremental compilation achieves fastest possible builds. Understanding incremental compilation is crucial for optimizing large project build times in CI/CD.
Unit tests verify individual units of code (functions, methods, classes) in isolation without external dependencies (databases, APIs, file systems). They are fast (thousands per second), deterministic, and provide immediate feedback on code correctness. Unit tests catch bugs early in development before code integration. Characteristics: fast execution (milliseconds), isolated (mock external dependencies), focused (test one thing), deterministic (same input always produces same output), independent (tests don't depend on each other or execution order). Example: testing calculation function with various inputs, testing validation logic, testing data transformations. CI/CD integration: unit tests run on every commit, gate merges (pull request checks), provide code coverage metrics. Fast execution enables running entire suite frequently without slowing development. Test failure blocks pipeline preventing broken code from progressing. Understanding unit testing enables catching bugs early at lowest cost.
Static code analysis examines source code without execution, detecting bugs, security vulnerabilities, code smells, and style violations. Tools: SonarQube (multi-language), ESLint (JavaScript), Pylint (Python), RuboCop (Ruby), Checkstyle (Java). Analysis identifies: potential null pointer exceptions, SQL injection vulnerabilities, unused variables, code duplication, complexity issues, style violations. SonarQube analyzes code quality dimensions: bugs (potential errors), vulnerabilities (security issues), code smells (maintainability issues), coverage (test coverage), duplications (code duplication). Quality gates define passing criteria (e.g., no critical bugs, coverage > 80%). Failed quality gate blocks merge or deployment. CI/CD integration: run analysis on every commit/pull request, fail build on critical issues, display results in dashboard, track quality trends over time. Early detection prevents bugs reaching production. Understanding static analysis enables automated code quality enforcement.
E2E tests validate complete application workflow from user perspective, testing UI, backend, database, external services together. Challenges: slow execution (minutes per test due to browser startup, network calls), flaky tests (intermittent failures from timing issues, network latency), high maintenance (break from UI changes), expensive infrastructure (browsers, test environments), complex debugging (failures involve multiple components). Flakiness causes: async operations, race conditions, external dependencies, test interdependencies, timing assumptions. Solutions: explicit waits (not sleep), retry mechanisms, test isolation, stable selectors, network stubbing. Tools: Selenium, Cypress, Playwright, TestCafe. CI/CD strategies: run critical path E2E tests only, run full suite nightly or before release, parallelize tests across machines, use headless browsers, implement retry logic, monitor flakiness. Benefits: validate real user scenarios, catch integration issues unit tests miss. Understanding E2E challenges enables realistic testing strategy.
Blue-Green deployment maintains two identical production environments: Blue (current production) and Green (new version). Deploy new version to Green environment while Blue serves traffic. After testing Green, switch traffic from Blue to Green instantly via load balancer or DNS. If issues arise, switch back to Blue immediately. This enables zero-downtime deployments and instant rollback. Process: deploy to Green, test Green thoroughly, switch traffic to Green, keep Blue as backup, use Blue for next deployment. Benefits: zero downtime, instant rollback, full testing before traffic switch, reduced risk. Drawbacks: requires double infrastructure (costly), database migrations complex (must be backward compatible), stateful applications challenging. Use cases: critical applications requiring zero downtime, high-risk deployments needing quick rollback, applications where gradual rollout not needed. Understanding Blue-Green enables reliable zero-downtime deployments.
Rolling update gradually replaces old version instances with new version in batches while maintaining availability. Update small batch (e.g., 2 servers), wait for health checks, proceed to next batch, repeat until all updated. Load balancer removes unhealthy instances from rotation ensuring zero downtime. If update fails, stop rollout and rollback. Process: update batch 1 (2 servers), health check passes, update batch 2, continue until complete. Batch size determined by: total instances, acceptable capacity reduction, risk tolerance. Smaller batches slower but safer. Benefits: zero downtime, gradual deployment, no additional infrastructure, automatic via orchestrators (Kubernetes). Kubernetes rolling update: RollingUpdate strategy with maxSurge (extra instances during update) and maxUnavailable (instances down during update). Use cases: stateless applications, microservices, containerized applications. Understanding rolling updates enables gradual zero-downtime deployments.
Standard environment progression: Development (dev) for active development and feature testing, Staging (stage/pre-prod) for integration testing and production-like testing, Production (prod) serving real users. Some organizations add: QA environment for quality assurance testing, UAT (User Acceptance Testing) for business validation, Canary environment for progressive rollouts. Environment characteristics: Dev (frequent deployments, may be unstable, lower resources), Staging (production-like, realistic data volumes, full integration testing), Production (high availability, monitoring, backups, security). Environment parity: staging should mirror production (infrastructure, configuration, data volumes) to catch production issues early. Deployment flow: commit → CI tests → deploy dev → integration tests → deploy staging → acceptance tests → deploy production. Benefits: risk reduction (catch issues before production), testing isolation (don't impact users), confidence building (progressive validation). Understanding environment management enables safe deployment progression.
Infrastructure as Code (IaC) defines and manages infrastructure using code files instead of manual processes. Infrastructure configurations stored in version control, reviewed through pull requests, deployed via automation. Tools: Terraform (cloud-agnostic), CloudFormation (AWS), ARM templates (Azure), Pulumi (multi-language). Benefits: version control (track changes, rollback), consistency (identical environments), automation (no manual steps), reproducibility (recreate infrastructure easily), documentation (code is documentation). IaC in CD: infrastructure changes go through same CI/CD pipeline as application code - test, review, deploy. Approach: declarative (define desired state, tool ensures state) vs imperative (define steps to reach state). Best practices: modularize infrastructure code, test infrastructure changes, use variables for environment differences, implement state management, separate infrastructure and application deployments. Understanding IaC enables reliable, repeatable infrastructure deployments.
Stages organize the pipeline into logical phases representing different parts of the CI/CD process. Common stages include Build (compile code), Test (run tests), Package (create artifacts), Deploy (deploy to environments), and Release (publish releases). Stages provide clear visualization in Jenkins UI showing which phase succeeded or failed, and how long each took. Each stage contains steps block with actual commands to execute. Stages run sequentially by default but can be parallelized. Example: ```groovy stages { stage('Build') { steps { sh 'mvn compile' } } stage('Test') { steps { sh 'mvn test' } } stage('Deploy') { steps { sh './deploy.sh' } } } ``` Stages improve pipeline readability, enable stage-level visualization in Blue Ocean, support stage-level post actions, allow conditional stage execution with when directive, and can have stage-specific agents. Understanding stages is essential for organizing complex pipelines into manageable, understandable phases.
First create the artifact in the build step. Then validate it with tests. If all green, deploy to an environment. This flow reduces risk by catching issues before they reach users.
If lint, unit tests, or security checks fail, the pipeline should halt. Fast failure gives quick signal and avoids wasting compute on later stages.
Make deploy idempotent and atomic. Use versioned, immutable artifacts. If a step fails, you can retry without double-deploying. Add clear prechecks and postchecks, and keep a quick rollback path to the last good version.
deploy.sh --version=$ARTIFACT_VERSION healthcheck /ready if fail -> rollback to PREV_VERSION
The input step pauses pipeline execution waiting for human approval or input before proceeding. Common uses include manual approval before production deployment, confirming release version, or providing environment-specific parameters. Pipeline shows prompt in UI with options to proceed or abort, and optionally request input parameters. Basic approval: ```groovy stage('Deploy to Production') { steps { input message: 'Deploy to production?', ok: 'Deploy' sh './deploy-prod.sh' } } ``` With parameters: ```groovy def userInput = input( message: 'Select deployment environment', parameters: [ choice(choices: ['staging', 'production'], name: 'ENVIRONMENT'), string(name: 'VERSION', defaultValue: '1.0.0', description: 'Version to deploy') ] ) sh "./deploy.sh ${userInput.ENVIRONMENT} ${userInput.VERSION}" ``` Input options include submitter (restrict who can approve), submitterParameter (capture approver's username), timeout (abort if no response within time). Use outside node block in Declarative Pipeline to avoid blocking executor while waiting. Understanding input step enables implementing approval gates and human-in-the-loop processes in automated pipelines.
Post sections define actions to run after pipeline or stage completion based on build result. Post conditions include always (always run), success (only if successful), failure (only if failed), unstable (tests failed but build passed), changed (status changed from previous run), fixed (previous build failed, this succeeded), regression (previous build succeeded, this failed), aborted (user aborted), unsuccessful (not successful), and cleanup (runs after all other conditions). Example: ```groovy post { success { echo 'Build succeeded!' slackSend color: 'good', message: 'Build successful' } failure { echo 'Build failed!' mail to: 'team@example.com', subject: "Failed: ${env.JOB_NAME} #${env.BUILD_NUMBER}", body: "Check console output at ${env.BUILD_URL}" } always { junit '**/target/test-results/*.xml' cleanWs() } } ``` Post sections can be defined at pipeline level (apply to entire pipeline) or stage level (apply to specific stage). Common uses include sending notifications, publishing test results, cleaning workspaces, archiving artifacts, and triggering downstream jobs. Understanding post actions enables proper cleanup, notification, and result handling regardless of build outcome.
The when directive controls conditional stage execution, running stages only when conditions are met. This enables branch-specific logic, environment-specific deployments, or skipping stages based on parameters. Conditions include branch (check branch name), environment (check environment variable), expression (evaluate Groovy expression), and more. Common when conditions: branch 'main' (only on main branch), environment name: 'DEPLOY_ENV', value: 'prod' (only when variable set), expression { return params.DEPLOY } (based on parameter), not { branch 'develop' } (inverse condition). Example: ```groovy stage('Deploy to Production') { when { branch 'main' environment name: 'DEPLOY_PROD', value: 'true' } steps { sh './deploy-prod.sh' } } ``` Additional conditions: allOf (all conditions must be true), anyOf (at least one condition true), changelog (commit message matches pattern), changeset (modified files match pattern), tag (TAG_NAME matches pattern), triggeredBy (specific trigger type). The beforeAgent option evaluates conditions before allocating agent, saving resources. Understanding when directives enables flexible, intelligent pipelines that adapt behavior based on context, crucial for managing different branches, environments, and deployment scenarios.
GitLab CI/CD supports multiple pipeline triggers. Push events trigger pipelines on commits to branches. Merge request pipelines run when MR is created or updated, testing changes before merging. Scheduled pipelines run at specified times (cron syntax) for nightly builds or periodic tasks. Manual pipelines are triggered through UI, API, or when job has when: manual directive. External webhooks trigger pipelines from external systems. Pipeline rules control when pipelines run using rules keyword: ```yaml job: script: echo "Hello" rules: - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' - if: '$CI_COMMIT_BRANCH == "main"' ``` Only keyword (legacy) specifies refs or branches: ```yaml job: script: echo "Deploy" only: - main - tags ``` Trigger tokens enable triggering pipelines via API with authentication. Multi-project pipelines trigger other projects' pipelines. Parent-child pipelines include child pipeline configurations. Understanding triggers enables flexible pipeline orchestration responding to various events.
The moment code is pushed or a pull request opens, the V C S calls the CI server. This gives fast feedback so developers fix issues while the context is fresh.
Jenkins supports multiple build trigger types. Manual trigger allows starting builds on-demand through UI or API. SCM polling periodically checks repository for changes using cron syntax (e.g., H/5 * * * * checks every 5 minutes), triggering builds when changes detected. Polling is simple but inefficient and has delay between commit and build. Webhooks (push notifications) are preferred where SCM (GitHub, GitLab, Bitbucket) notifies Jenkins immediately when code is pushed, triggering builds instantly without polling overhead. Configure webhooks in SCM settings pointing to Jenkins endpoint (github-webhook/ for GitHub). Webhooks require Jenkins accessible from SCM server and proper webhook secret configuration for security. Scheduled builds (Build periodically) trigger at specified times using cron syntax regardless of code changes, useful for nightly builds or periodic testing. Trigger builds after other projects complete (Build after other projects are built) enables pipeline chaining. Understanding triggers is essential for automation - webhooks are best practice for immediate feedback, polling as fallback, and scheduled builds for time-based operations.
Guard deploy steps with rules. Build and test can run everywhere, but deployment should gate on main or release branches. This prevents accidental production pushes.
if: github.ref == 'refs/heads/main' steps: - run: ./deploy.sh
Declarative Pipeline uses structured syntax starting with pipeline block as the root. Agent directive specifies where pipeline or stage executes: agent any runs on any available agent, agent { label 'linux' } runs on agents with 'linux' label, agent none means no global agent (define per stage), agent { docker 'maven:3.8.1' } runs in Docker container. Agent can be defined globally for entire pipeline or per stage for flexibility. Stages and steps structure the pipeline. Stages block contains multiple stage blocks representing phases of the pipeline (Build, Test, Deploy). Each stage contains steps block with actual commands: sh for shell commands (Unix), bat for Windows batch commands, script for embedded Groovy code. Example: ```groovy pipeline { agent any stages { stage('Build') { steps { sh 'mvn clean package' } } stage('Test') { steps { sh 'mvn test' } } } } ``` Environment variables defined at pipeline level (global) or stage level (local to stage). Use environment block: environment { JAVA_HOME = '/usr/lib/jvm/java-11' }. Access with ${env.VARIABLE} or $VARIABLE. Credentials injected as environment variables using credentials() function: environment { API_KEY = credentials('api-key-id') }. Post section defines actions after stage or pipeline completes based on status. Conditions include always (always run), success (only if successful), failure (only if failed), unstable (tests failed but build passed), changed (status changed from previous run). Example: ```groovy post { success { echo 'Pipeline succeeded!' slackSend message: 'Build successful' } failure { echo 'Pipeline failed!' mail to: 'team@example.com', subject: 'Build Failed' } always { junit '**/target/test-results/*.xml' cleanWs() } } ``` Other directives include options (pipeline-level settings like timestamps, timeout), parameters (build parameters for parameterized builds), triggers (build triggers like cron, pollSCM), tools (automatic tool installation like Maven, JDK), when (conditional stage execution). Understanding Declarative syntax enables writing maintainable, readable pipelines.
Declarative Pipeline is the newer, recommended syntax with a more structured, opinionated format. It starts with a pipeline block and requires specific sections like agent, stages, and steps. Declarative syntax is easier to read and write, has built-in validation, supports restart from stage, and provides a clearer structure. Example: pipeline { agent any; stages { stage('Build') { steps { sh 'make' } } } }. Scripted Pipeline is the original, more flexible syntax based on Groovy. It starts with a node block and allows full Groovy programming capabilities including variables, loops, functions, and complex logic. Scripted pipelines offer more flexibility but require more Groovy knowledge and can become harder to maintain. Example: node { stage('Build') { sh 'make' } }. Declarative is recommended for most use cases due to better structure, easier learning curve, and built-in best practices. Use Scripted only when you need complex logic that Declarative doesn't support well, though Declarative's script step allows embedded Groovy for when you need flexibility. Understanding both types is important as you'll encounter legacy Scripted pipelines while new development should prefer Declarative.
Declarative Pipeline always starts with a pipeline block as the root element. This block contains all pipeline configuration including agent, stages, post actions, and other directives. The pipeline block is mandatory and defines the entire pipeline structure using a predefined, opinionated syntax that's easier to read and write than Scripted Pipeline. Inside the pipeline block, you define key sections: agent (where to run), stages (pipeline phases), post (post-build actions), environment (environment variables), options (pipeline-level settings), parameters (build parameters), and triggers (build triggers). Example: ```groovy pipeline { agent any stages { stage('Build') { steps { sh 'make' } } } } ``` The pipeline block enforces structure making pipelines more consistent and maintainable. In contrast, Scripted Pipeline uses node block as the root and allows arbitrary Groovy code. Understanding the pipeline block structure is fundamental to writing Declarative Pipelines correctly.
Matrix strategy in GitHub Actions creates multiple job runs with different variable combinations. This enables testing across multiple versions, platforms, or configurations without duplicating job definitions. Matrix variables are accessible as ${{ matrix.variable }} in job steps. Example testing multiple Node.js versions and operating systems: ```yaml jobs: test: runs-on: ${{ matrix.os }} strategy: matrix: node-version: [14, 16, 18] os: [ubuntu-latest, windows-latest, macos-latest] steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} - run: npm test ``` Creates 9 jobs (3 Node versions × 3 operating systems). Matrix can include/exclude specific combinations: ```yaml matrix: node-version: [14, 16, 18] os: [ubuntu-latest, windows-latest] exclude: - os: windows-latest node-version: 14 ``` Fail-fast: true (default) cancels remaining jobs if any fails. fail-fast: false allows all jobs to complete. Max-parallel limits concurrent jobs. Understanding matrix strategy enables comprehensive testing across multiple configurations efficiently.
Jenkins agents can connect to master through multiple methods. SSH connection is common for Unix/Linux agents where master initiates SSH connection to agent using credentials. This is simple and secure but requires SSH access. JNLP (Java Network Launch Protocol) or Java Web Start allows agents to initiate connection to master, useful when master can't reach agent directly (firewalls, NAT). Agent runs Java process connecting outbound to master. Windows agents often run as Windows service, and Docker agents can be dynamically provisioned using Docker plugin. Kubernetes plugin can provision agent pods on-demand in Kubernetes clusters. Cloud providers have plugins for provisioning agents on AWS EC2, Azure VMs, or GCP instances dynamically based on build demand. Agent setup requires Java installed on agent machine, proper network connectivity between master and agent, and correct credentials configured in Jenkins. Master needs to know agent's connection details (hostname/IP, credentials, labels). Agents can have labels for job targeting specific environments (linux, windows, docker, high-memory). Understanding agent connection methods is crucial for distributed Jenkins architectures.
Jenkins follows a master-agent architecture (formerly called master-slave) where the Jenkins master is the central control unit that manages configuration, schedules build jobs, monitors agents, and serves the Jenkins UI. The master handles job scheduling, dispatching builds to agents, monitoring agent status, recording and presenting build results, and can also execute builds directly. Agents (also called nodes or slaves) are worker machines that execute builds dispatched by the master. Agents connect to the master and execute tasks as directed. You can have multiple agents with different configurations, operating systems, or tools installed, allowing builds to run on the most appropriate environment. This distributed architecture enables parallel execution of builds and scales Jenkins to handle large workloads. Benefits include load distribution across multiple machines, ability to build on different platforms (Windows, Linux, macOS) from single master, isolation of build environments, and scalability by adding more agents. The master should not execute heavy builds directly in production to keep it responsive for managing the system. Understanding this architecture is crucial for scaling Jenkins deployments.
Multibranch Pipeline automatically discovers branches and pull requests in SCM repository, creating pipeline instances for each with Jenkinsfile. When a new branch is created, Jenkins detects it and creates corresponding pipeline. When branch is deleted, the pipeline is removed. This eliminates manual job creation for each branch and ensures all branches use the same pipeline definition. Configuration: create Multibranch Pipeline item, specify repository URL and credentials, configure branch discovery (which branches to build), optionally filter branches with regex or wildcards, set scan interval for detecting new branches. Jenkins scans repository periodically or on webhook, finding Jenkinsfiles and creating/updating pipelines accordingly. Benefits include automatic branch discovery (no manual job creation), consistent pipeline across branches (all use same Jenkinsfile), pull request building (test changes before merge), branch-specific behavior (when directive for branch-specific logic), automatic cleanup (orphaned pipelines removed). Example branch-specific logic: ```groovy stage('Deploy') { when { branch 'main' } steps { sh './deploy-prod.sh' } } ``` Pull request support: Multibranch Pipeline can build pull requests, reporting status back to SCM (GitHub, GitLab, Bitbucket). PR builds test changes in isolation before merging. Configure PR discovery in Multibranch settings. Understanding Multibranch Pipelines is essential for modern Git workflows with feature branches and pull requests.
Pipeline plugins are fundamental: Pipeline plugin provides pipeline functionality, Pipeline: Stage View visualizes pipeline stages, Blue Ocean offers modern UI for pipelines with visual editor, and Pipeline: Multibranch provides automatic pipeline creation for branches. These plugins enable Pipeline as Code and modern pipeline experiences. Source Control Management plugins include Git plugin (Git integration), GitHub plugin (GitHub-specific features like webhook, status reporting), GitLab plugin, and Bitbucket plugin. These integrate Jenkins with code repositories, enabling automatic triggers on commits, pull request builds, and status reporting back to SCM. Build and testing plugins: Maven Integration, Gradle Plugin, NodeJS Plugin provide build tool support. JUnit Plugin publishes test results, Cobertura or JaCoCo plugins show code coverage, HTML Publisher displays reports. These plugins integrate build tools and present results in Jenkins UI. Artifact and container plugins: Artifactory Plugin or Nexus Artifact Uploader manage artifact publication, Docker Plugin and Docker Pipeline enable Docker in pipelines, Kubernetes Plugin provisions dynamic agents in Kubernetes. These plugins handle artifact management and containerized builds. Notification and reporting plugins: Email Extension sends detailed email notifications, Slack Notification sends messages to Slack, Jira Plugin integrates with Jira for issue tracking, Prometheus Plugin exposes metrics. These plugins provide feedback loops and monitoring. Security and credentials plugins: Credentials Plugin (usually pre-installed) manages secrets, Role-based Authorization Strategy controls access, LDAP or Active Directory integrates with enterprise auth, Hashicorp Vault stores secrets in Vault. These plugins secure Jenkins and manage access control. Configuration and management: Configuration as Code (JCasC) manages Jenkins configuration as YAML, Job DSL creates jobs programmatically, Folders Plugin organizes jobs in folders, Build Timeout aborts hanging builds. These plugins enable Infrastructure as Code and maintainability. Plugins work together: Git plugin clones code, Pipeline executes Jenkinsfile from repo, Maven plugin builds, JUnit publishes test results, Docker plugin builds image, Slack plugin sends notification. Understanding plugin ecosystem and integration enables building comprehensive CI/CD pipelines.
GitLab CI/CD organizes pipelines into stages that run sequentially. Default stages are build, test, and deploy, running in that order. Jobs within the same stage run in parallel (if multiple runners available), while stages wait for all jobs in previous stage to complete before starting. This structure enables parallel execution within stages while maintaining dependencies between stages. Example: ```yaml stages: - build - test - deploy build_app: stage: build script: - make build unit_tests: stage: test script: - make test integration_tests: stage: test script: - make integration-test deploy_prod: stage: deploy script: - make deploy ``` Build stage completes first, then unit_tests and integration_tests run in parallel (both in test stage), finally deploy_prod runs. If any job fails, subsequent stages don't execute by default. Custom stages can be defined in any order. Understanding stage execution model is crucial for designing efficient pipelines.
Maven project structure follows convention over configuration. Standard directory layout: ``` project/ ├── pom.xml ├── src/ │ ├── main/ │ │ ├── java/ # Source code │ │ ├── resources/ # Config files │ │ └── webapp/ # Web resources (for WAR) │ └── test/ │ ├── java/ # Test code │ └── resources/ # Test resources └── target/ # Build output ``` pom.xml (Project Object Model) defines project configuration: ```xml <project> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>myapp</artifactId> <version>1.0.0-SNAPSHOT</version> <packaging>jar</packaging> <properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.7.0</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency> </dependencies> </project> ``` Maven lifecycle phases: validate (validate project correctness), compile (compile source code), test (run unit tests), package (create JAR/WAR), verify (run integration tests), install (install to local repository), deploy (deploy to remote repository). Each phase executes all previous phases. Dependency management uses transitive dependencies (automatically includes dependencies of dependencies). Dependency scopes: compile (default, available everywhere), provided (available at compile but not included in package), runtime (needed at runtime but not compile), test (only for tests), system (like provided but specified via systemPath). Example: ```xml <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> # Servlet container provides this </dependency> ``` Dependency exclusions prevent transitive dependency issues: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency> ``` Dependency management section defines versions centrally: ```xml <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.7.0</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> ``` Plugins extend Maven functionality: ```xml <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.10.1</version> <configuration> <source>11</source> <target>11</target> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> ``` Repository configuration: ```xml <repositories> <repository> <id>central</id> <url>https://repo1.maven.org/maven2</url> </repository> <repository> <id>company-repo</id> <url>https://nexus.company.com/repository/maven-public/</url> </repository> </repositories> <distributionManagement> <repository> <id>releases</id> <url>https://nexus.company.com/repository/maven-releases/</url> </repository> <snapshotRepository> <id>snapshots</id> <url>https://nexus.company.com/repository/maven-snapshots/</url> </snapshotRepository> </distributionManagement> ``` CI/CD integration: ```yaml # GitLab CI build: image: maven:3.8-jdk-11 script: - mvn clean package artifacts: paths: - target/*.jar cache: key: ${CI_COMMIT_REF_SLUG} paths: - .m2/repository ``` Settings.xml for credentials (~/.m2/settings.xml): ```xml <settings> <servers> <server> <id>releases</id> <username>${env.NEXUS_USER}</username> <password>${env.NEXUS_PASSWORD}</password> </server> </servers> <mirrors> <mirror> <id>company-mirror</id> <mirrorOf>*</mirrorOf> <url>https://nexus.company.com/repository/maven-public/</url> </mirror> </mirrors> </settings> ``` Best practices: use dependency management for version consistency, leverage Bill of Materials (BOM) for related dependencies, cache .m2/repository in CI/CD, use Maven wrapper for version consistency, skip tests during package if already run (mvn package -DskipTests), parallel builds (mvn -T 4 package), offline mode for faster builds with cached dependencies (mvn -o package). Understanding Maven enables efficient Java project automation.
Gradle Wrapper (gradlew) is a script that downloads and runs specific Gradle version, ensuring build consistency across different environments. Instead of requiring Gradle pre-installed on CI agents, wrapper downloads correct version on first run. This eliminates version mismatch issues and 'works on my machine' problems. Wrapper consists of gradlew (Unix), gradlew.bat (Windows), gradle/wrapper/gradle-wrapper.jar, and gradle/wrapper/gradle-wrapper.properties specifying Gradle version. Example gradle-wrapper.properties: ``` distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip ``` CI/CD usage: ./gradlew build ensures same Gradle version used locally and in CI. Commit wrapper files to version control so anyone cloning repository uses correct version. Generate wrapper with gradle wrapper command. Benefits include version consistency, no installation required, explicit version control, easy upgrades (update properties file). Understanding wrapper is crucial for reproducible builds.
Dependency caching stores downloaded dependencies (Maven .m2, npm node_modules, pip cache) between pipeline runs, significantly reducing build time by avoiding re-downloading unchanged dependencies. Without caching, each build downloads all dependencies from scratch, wasting time and bandwidth. Caching strategies: use lock files (package-lock.json, Gemfile.lock, yarn.lock) as cache keys - cache invalidates only when dependencies change. Example GitLab: ```yaml cache: key: files: - package-lock.json paths: - node_modules/ ``` GitHub Actions: ```yaml - uses: actions/setup-node@v3 with: node-version: '18' cache: 'npm' ``` Best practices: cache by lock file hash, separate cache per branch/project, set reasonable cache expiration, cache both dependencies and build outputs when possible, use shared cache storage (S3, GCS) for distributed runners. Benefits include faster builds (30-80% reduction in build time), reduced network usage, more reliable builds (less dependent on external registry availability). Understanding caching is essential for optimizing pipeline performance.
Artifact repositories (Nexus, Artifactory, AWS CodeArtifact) centrally store and manage build artifacts (JARs, WARs, npm packages, Docker images) and dependencies. They serve as proxy for external repositories (Maven Central, npm registry) caching dependencies locally for faster builds and reliability when external sources are unavailable. Key features include version management (store multiple versions), access control (who can publish/download), proxy caching (cache external dependencies), promotion workflows (move artifacts between repositories like snapshot to release), metadata management, and audit trails. Repositories support multiple formats: Maven, npm, PyPI, Docker, NuGet, and more. CI/CD integration: build pipeline publishes artifacts to repository after successful build, deployment pipeline pulls artifacts from repository for deployment. Example Maven publish: ```xml <distributionManagement> <repository> <id>releases</id> <url>https://nexus.example.com/repository/maven-releases/</url> </repository> </distributionManagement> ``` Benefits include centralized artifact storage, build reproducibility, dependency caching, reduced external dependency failures, and artifact lifecycle management. Understanding artifact repositories is essential for enterprise CI/CD.
Multi-stage builds keep images small and secure. Caching dependency layers speeds up rebuilds. Never bake secrets into images; inject them at runtime.
FROM node:20 as deps WORKDIR /app COPY package*.json . RUN npm ci COPY . . RUN npm run build FROM node:20-alpine COPY --from=deps /app/dist ./dist
Integration tests verify multiple components work together correctly, including external dependencies (databases, APIs, message queues). They test integration points: API endpoints with database, service-to-service communication, external API integration, file system operations. Integration tests catch issues unit tests miss: database schema mismatches, API contract violations, configuration problems. Examples: testing API endpoint that queries database, testing payment service integration with payment gateway, testing message queue producer/consumer, testing file upload to cloud storage. Integration tests use real dependencies (test database, mock external APIs) or containers for isolation. CI/CD execution: run after unit tests in separate stage, use Docker Compose or Testcontainers for dependencies, run against test database, clean up resources after tests. Slower than unit tests (seconds) but faster than E2E (minutes). Understanding integration testing ensures components interact correctly.
Testing Pyramid visualizes ideal test distribution: broad base of many fast unit tests, narrower middle layer of integration tests, narrow top of few end-to-end tests. Unit tests (70%) are fast, cheap, isolated testing individual functions/classes. Integration tests (20%) verify components work together, testing APIs, database interactions. E2E tests (10%) test entire application flow, slow and expensive but validate real user scenarios. Rationale: unit tests provide fast feedback (milliseconds), catch most bugs early, easy to maintain. E2E tests are slow (seconds/minutes), brittle (break from UI changes), expensive to maintain. More unit tests ensure solid foundation, fewer E2E tests validate critical paths. Anti-pattern: inverted pyramid with mostly E2E tests results in slow, flaky test suites. Pyramid implementation in CI/CD: run unit tests first (fail fast), run integration tests after unit tests pass, run E2E tests last or only on main branch. Fast feedback loop encourages frequent commits. Understanding testing pyramid ensures efficient, maintainable test strategy.
Test reports provide visibility into test results enabling quick identification of issues. Essential information: overall pass/fail status, number of tests passed/failed/skipped, test coverage percentage, execution time per test/suite, failure details (error messages, stack traces, screenshots for UI tests), flaky test identification, historical trends. Report formats: JUnit XML (standard format), HTML reports (human-readable), JSON (programmatic access). Tools: Allure, TestNG, JUnit, pytest, Jest. CI/CD platforms (Jenkins, GitLab, GitHub Actions) parse JUnit XML displaying results in UI, annotating pull requests with test failures. Report benefits: quick failure identification, coverage tracking, performance regression detection, flakiness monitoring, historical trend analysis. Best practices: preserve reports as artifacts, annotate pull requests, send notifications on failure, generate coverage badges, track trends over time. Understanding test reporting enables data-driven test improvement.
Security testing in CI/CD includes multiple layers. SAST (Static Application Security Testing) analyzes source code for vulnerabilities: SQL injection, XSS, insecure cryptography. Tools: SonarQube, Snyk Code, Semgrep. DAST (Dynamic Application Security Testing) tests running application: OWASP ZAP, Burp Suite. Dependency scanning detects vulnerable libraries: Snyk, Dependabot, npm audit. Container scanning finds vulnerabilities in Docker images: Trivy, Aqua, Clair. Secrets detection prevents committing passwords, API keys, tokens: GitGuardian, git-secrets, detect-secrets. License compliance ensures dependencies have acceptable licenses. Infrastructure scanning validates Terraform/CloudFormation: Checkov, tfsec. Comprehensive security testing provides defense in depth. CI/CD integration: run SAST on every commit, dependency scan on dependency changes, DAST on staging deployment, container scan before pushing images. Fail builds on critical vulnerabilities. Understanding security testing enables shift-left security approach.
Blue-Green deployment maintains two identical production environments enabling instant traffic switching. Infrastructure setup requires: two complete environments (compute, networking, databases), load balancer or DNS for traffic routing, monitoring for both environments, automation for deployment and switching. Infrastructure patterns: 1. Load Balancer switching (AWS ELB): ```yaml # Terraform example resource "aws_lb_target_group" "blue" { name = "app-blue" port = 80 protocol = "HTTP" vpc_id = aws_vpc.main.id health_check { path = "/health" healthy_threshold = 2 unhealthy_threshold = 2 timeout = 5 } } resource "aws_lb_target_group" "green" { name = "app-green" port = 80 protocol = "HTTP" vpc_id = aws_vpc.main.id health_check { path = "/health" healthy_threshold = 2 unhealthy_threshold = 2 } } resource "aws_lb_listener" "main" { load_balancer_arn = aws_lb.main.arn port = 80 protocol = "HTTP" default_action { type = "forward" target_group_arn = var.active_environment == "blue" ? aws_lb_target_group.blue.arn : aws_lb_target_group.green.arn } } ``` 2. Kubernetes Service switching: ```yaml apiVersion: v1 kind: Service metadata: name: myapp spec: selector: app: myapp version: blue # Switch to 'green' for deployment ports: - port: 80 targetPort: 8080 --- apiVersion: apps/v1 kind: Deployment metadata: name: myapp-blue spec: replicas: 3 selector: matchLabels: app: myapp version: blue template: metadata: labels: app: myapp version: blue spec: containers: - name: myapp image: myapp:v1 --- apiVersion: apps/v1 kind: Deployment metadata: name: myapp-green spec: replicas: 3 selector: matchLabels: app: myapp version: green template: metadata: labels: app: myapp version: green spec: containers: - name: myapp image: myapp:v2 ``` Deployment process: 1. Deploy to inactive environment (Green) 2. Run smoke tests on Green 3. Run full test suite on Green 4. Switch small percentage for canary testing (optional) 5. Monitor Green environment metrics 6. Switch all traffic to Green 7. Monitor for issues 8. Keep Blue running as backup Database considerations (biggest challenge): Backward compatible migrations: ```sql -- Phase 1: Add new column (nullable) ALTER TABLE users ADD COLUMN email_verified BOOLEAN DEFAULT NULL; -- Deploy Green with code using new column -- Both Blue and Green work -- Phase 2: Backfill data UPDATE users SET email_verified = FALSE WHERE email_verified IS NULL; -- Phase 3: Make non-nullable (after Blue retired) ALTER TABLE users ALTER COLUMN email_verified SET NOT NULL; ``` Database patterns: 1. Shared database (both Blue and Green use same DB): - Simple but requires backward compatible migrations - Blue and Green must handle old and new schema 2. Database per environment: - Complete isolation - Complex data synchronization - Expensive (duplicate data) 3. Read replicas: - Blue uses primary, Green uses replica for testing - Promote replica during switch Traffic switching automation: ```bash #!/bin/bash # blue-green-switch.sh CURRENT_ENV=$1 NEW_ENV=$2 echo "Current: $CURRENT_ENV, Switching to: $NEW_ENV" # Deploy to new environment kubectl apply -f k8s/$NEW_ENV/ # Wait for pods ready kubectl wait --for=condition=ready pod -l version=$NEW_ENV --timeout=300s # Run smoke tests ./smoke-tests.sh https://$NEW_ENV.internal.example.com if [ $? -ne 0 ]; then echo "Smoke tests failed, aborting" exit 1 fi # Switch traffic kubectl patch service myapp -p '{"spec":{"selector":{"version":"'$NEW_ENV'"}}}' echo "Switched to $NEW_ENV" # Monitor for 5 minutes sleep 300 # Check error rate ERROR_RATE=$(curl -s "https://metrics.example.com/error-rate?env=$NEW_ENV") if [ "$ERROR_RATE" -gt 5 ]; then echo "High error rate: $ERROR_RATE%, rolling back" kubectl patch service myapp -p '{"spec":{"selector":{"version":"'$CURRENT_ENV'"}}}' exit 1 fi echo "Deployment successful" ``` CI/CD integration: ```yaml stages: - build - deploy-green - test-green - switch-traffic - cleanup build: stage: build script: - docker build -t myapp:$CI_COMMIT_SHA . - docker push myapp:$CI_COMMIT_SHA deploy-green: stage: deploy-green script: - export NEW_ENV="green" - export OLD_ENV="blue" - ./deploy.sh $NEW_ENV $CI_COMMIT_SHA environment: name: green url: https://green.internal.example.com test-green: stage: test-green script: - ./smoke-tests.sh https://green.internal.example.com - ./integration-tests.sh https://green.internal.example.com switch-traffic: stage: switch-traffic script: - ./blue-green-switch.sh blue green when: manual environment: name: production only: - main cleanup-blue: stage: cleanup script: - kubectl scale deployment myapp-blue --replicas=1 when: manual ``` Rollback procedure: ```bash #!/bin/bash # Quick rollback kubectl patch service myapp -p '{"spec":{"selector":{"version":"blue"}}}' echo "Rolled back to blue" ``` Monitoring during switch: ```python import requests import time def monitor_switch(environment, duration=300): metrics = ['error_rate', 'latency_p95', 'throughput'] start = time.time() while time.time() - start < duration: for metric in metrics: value = get_metric(environment, metric) if is_anomaly(metric, value): print(f"Anomaly detected: {metric}={value}") rollback() return False time.sleep(10) return True ``` Best practices: test rollback procedures regularly, automate everything, implement health checks, monitor business metrics not just technical, use gradual switch (weighted routing) for additional safety, maintain backward compatible migrations, document database migration strategy, implement automated rollback on anomalies, keep Blue running for configured time after switch, use feature flags for additional safety layer. Understanding Blue-Green enables zero-downtime deployments with instant rollback capability.
Traffic switching in Blue-Green deployment provides instant rollback (seconds) by redirecting load balancer to previous environment. No redeployment needed since old version still running. Other rollback methods: revert image tag (container rollback, minutes), redeploy previous version (rebuild pipeline, 10-30 minutes), database rollback (complex, may lose data), feature flag disable (instant for feature-flagged changes). Rollback decision factors: severity (critical bugs require immediate rollback), user impact (how many affected), fix time (quick fix vs rollback), data integrity (can we rollback database?). Automated vs manual: automated rollback based on metrics (error rate spike), manual rollback for complex issues requiring human judgment. Rollback best practices: maintain previous version availability, automate rollback process, test rollback procedures regularly, implement health checks triggering rollback, preserve data compatibility, communicate rollback to team. Understanding rollback strategies minimizes incident recovery time.
Parallel execution uses parallel block containing multiple stage blocks that run concurrently. This reduces total pipeline execution time for independent tasks like running different test suites, building for multiple platforms, or deploying to multiple environments simultaneously. Example: ```groovy stage('Tests') { parallel { stage('Unit Tests') { steps { sh 'npm run test:unit' } } stage('Integration Tests') { steps { sh 'npm run test:integration' } } stage('E2E Tests') { steps { sh 'npm run test:e2e' } } } } ``` Parallel stages execute on different agents if available (requires agent defined per stage or sufficient executors). If one parallel stage fails, others continue unless failFast option is set. Parallel execution requires tasks be independent without shared state. Use failFast: true inside parallel block to abort all parallel stages if any fails. Benefits include reduced pipeline duration (stages run simultaneously instead of sequentially), better resource utilization (multiple agents working concurrently), and faster feedback (test results available sooner). Understanding parallel execution is crucial for optimizing pipeline performance.
Shared Libraries enable sharing common pipeline code across multiple pipelines in an organization. Libraries are stored in version control (Git) with defined structure (vars/, src/, resources/ directories) and loaded into pipelines using @Library annotation. This promotes DRY (Don't Repeat Yourself) principles, standardizes pipeline patterns, and enables centralized maintenance of common functionality. Library structure: vars/ directory contains global variables (functions callable from pipelines like buildAndTest()), src/ contains Groovy classes for complex logic, resources/ contains non-Groovy files. Example vars/buildMaven.groovy: ```groovy def call(Map config) { pipeline { agent any stages { stage('Build') { steps { sh "mvn ${config.goals ?: 'clean package'}" } } } } } ``` Usage in Jenkinsfile: ```groovy @Library('my-shared-library') _ buildMaven(goals: 'clean install') ``` Shared Libraries provide versioning (use specific library version/branch), testing (test library code separately), abstraction (hide complexity from pipeline authors), and governance (enforce standards centrally). Understanding Shared Libraries enables building maintainable, scalable pipeline ecosystems.