Docker: A Playground for App Deployment

mibii - Jul 9 - - Dev Community

To succeed in app deployment we need clear understand the deployed app environments and dependencies Docker is a good to play with app deployment before Kubernaties and cloud.
Understanding App Environments and Dependencies:

A clear understanding of the environment where your application will be deployed and the dependencies it relies on is crucial for successful deployment. This includes factors like:

  • Operating System: Is it Linux, Windows, or something else? (in case of Docker container deployment OS is doesn't matter )
  • Software Requirements: Does the app need specific libraries or databases to function?
  • Hardware Resources: How much CPU, memory, and storage are needed?

Understanding these aspects helps you choose the right deployment strategy and configure the environment accordingly.

Docker: A Playground for App Deployment:

Docker is an excellent tool for experimenting with app deployment before venturing into more complex environments like Kubernetes or the cloud.

Good understanding of Docker Compose will help you to understand the Jenkins.

There are similarity Between Docker Compose and Jenkins:**

While both play a role in app deployment, they have distinct functionalities:

  • Docker Compose: Focuses on defining and running multi-container applications. It uses a YAML file to specify services (containers) and their configurations. It's ideal for development and testing environments with a few interrelated services.
  • Jenkins: An automation server that can handle various tasks in the software development lifecycle, including building, testing, and deploying applications. It can integrate with Docker for building and deploying containerized applications. It's well-suited for larger deployments with complex workflows and continuous integration/continuous delivery (CI/CD) pipelines.

Analogy:

Think of Docker Compose as a recipe for a single dish (your application) with all its ingredients (dependencies) listed out. It's easy to follow and perfect for a small kitchen (development environment).

Jenkins, on the other hand, is like a full-fledged restaurant kitchen. It can handle various dishes (applications) with complex recipes (build and deployment workflows), managing ingredients (dependencies), and ensuring a smooth flow of orders (deployments).
Jenkins itself is an application, and its official images are typically hosted on Docker Hub (
https://hub.docker.com/r/jenkins/jenkins). Just like any other containerized application, you can pull Jenkins images from Docker Hub to run Jenkins within a container.

Docker Compose: Orchestrating Multi-Container Applications

Docker Compose shines when you have an application that relies on multiple services (containers). It simplifies the process, ensures consistent configuration, and provides an efficient workflow for development and testing. By understanding how to define services and configure them within a Docker Compose file, you can create a well-orchestrated deployment environment for your applications.

  • Imagine this: You have a web application that needs a database to store data. Traditionally, you'd install and configure both the web app and the database separately on your server.
  • Docker Compose to the Rescue: With Docker Compose, you can define both the web app and the database as separate services in a YAML file called docker-compose.yml.

  • Docker Compose allows you to quickly spin up a development environment with all the required services running, improving your development workflow.

Key Points to Consider:

  • Credentials Management: Never store sensitive information like database passwords directly in your docker-compose.yml file. Use environment variables stored securely in your deployment environment.
  • Network Configuration: You can configure networks within your Docker Compose file to allow communication between your services if needed.

This sample demonstrates a web application relying on a MySQL database:

version: "3.8"  # Specify the Docker Compose version

services:
  # Web application service
  web:
    build: .  # Build the image from the current directory (where the Dockerfile resides)
    ports:
      - "8080:80"  # Map container port 80 to host port 8080
    volumes:
      - ./app:/app  # Mount the current directory's "app" folder into the container's "/app" folder (for development)
    environment:
      - DB_HOST=db  # Set the environment variable "DB_HOST" to "db" (pointing to the database service)

  # Database service
  db:
    image: mysql:8.0  # Use the official MySQL image with version 8.0
    environment:
      - MYSQL_ROOT_PASSWORD=mypassword  # Set the root password for MySQL (**store securely in deployment!**)
      - MYSQL_DATABASE=mydatabase  # Name of the database to create
    volumes:
      - mysql-data:/var/lib/mysql  # Persistent volume for database data (prevents data loss on container restart)

volumes:
  # Define the persistent volume for database data
  mysql-data: {}
Enter fullscreen mode Exit fullscreen mode

when you define image: mysql:8.0 in your docker-compose.yml file, Docker Compose will automatically attempt to pull the required image from Docker Hub, the official registry for Docker images.

Jenkinsfile and bash scripting

Jenkinsfile and bash scripting share some similarities, but there are also key differences that make them suited for different purposes:

Similarities:

  • Both can execute commands: Both Jenkinsfiles and bash scripts can execute commands on the system, such as building applications, running tests, or deploying code.
  • Focus on steps: Both define a sequence of steps to be executed one after another.
  • Conditional branching: Both can use conditional statements to control the flow of execution based on certain conditions.

Differences:

  • Focus: Jenkinsfiles are specifically designed for defining pipelines within Jenkins, which automate software delivery workflows. Bash scripts are general-purpose scripting tools that can be used for various tasks beyond deployments.
  • Declarative vs. Imperative: Jenkinsfiles are declarative, specifying what needs to be done without explicitly outlining every step. Bash scripts are typically imperative, providing detailed instructions on how to achieve a specific outcome.
  • Integration with Jenkins features: Jenkinsfiles leverage Jenkins features like environment variables, credentials management, and plugins for extended functionality. Bash scripts don't have this built-in integration.
  • Readability and Maintainability: Jenkinsfiles are typically more concise and easier to maintain for complex workflows due to their declarative nature and structure. Bash scripts, especially for longer tasks, might become less readable and harder to manage.

example that incorporates common practices of Jenkins

pipeline {
    // Define agent with label (replace with your actual label)
    agent any { label 'build-agent' }

    // Enable failure tolerance (optional)
    options {
        retry(count: 2)  // Retry twice on failures
    }

    environment {
        // Define environment variables (replace with your actual names and values)
        JAVA_HOME = '/usr/lib/jvm/java-17-openjdk-amd64'
        DB_HOST = 'db.example.com'
        DB_NAME = 'mydatabase'
        SECRET_TOKEN = '******' // Use credential plugin for secrets
    }

    stages {
        stage('Checkout Code') {
            steps {
                git branch: 'main', credentialsId: 'github-credentials', url: 'https://github.com/your-organization/your-project.git'
            }
        }
        stage('Build') {
            steps {
                sh 'mvn clean package -DskipTests' // Build without unit tests
            }
        }
        stage('Unit Tests (Optional)') {
            when {
                expression { branch == 'main' || branch == 'release/*' } // Run tests only on main and release branches
            }
            steps {
                sh 'mvn test'
            }
        }
        stage('Integration Tests (Optional)') {
            when {
                expression { branch == 'main' } // Run integration tests only on main branch
            }
            steps {
                script {
                    // Download and configure integration test environment (replace with your script)
                    sh 'wget https://your-integration-test-environment.zip && unzip ...'
                }
                sh 'mvn verify -P integration-tests' // Run integration tests with specific profile
            }
        }
        stage('Security Scan (Optional)') {
            when {
                expression { branch == 'main' } // Run security scan only on main branch
            }
            steps {
                // Use a security scanning plugin like 'snyk' or 'anchore' (replace with your plugin)
                snyk analyze // Example usage of a security scanner plugin
            }
        }
        stage('Deploy to Staging (Optional)') {
            when {
                expression { branch == 'main' } // Deploy to staging only on main branch
            }
            steps {
                script {
                    // Upload build artifact to staging server (replace with your script)
                    sh 'scp target/*.war user@staging.server.com:/path/to/staging/directory/'
                    sh 'ssh user@staging.server.com "sudo systemctl restart your-app-service"' // Restart service on staging
                }
            }
        }
        stage('Deploy to Production (Manual)') {
            steps {
                // Manual approval gate (user interaction required)
                input {
                    submitter 'Operations Team' // Specify who can approve deployment
                }
                script {
                    // Notify operations team about deployment (optional)
                    slackSend channel: '#ops-channel', message: 'Deployment to Production is ready for approval!'
                }
            }
            steps {
                // Deploy to production after manual approval (replace with your script)
                script {
                    sh 'scp target/*.war user@production.server.com:/path/to/production/directory/'
                    sh 'ssh user@production.server.com "sudo systemctl restart your-app-service"' // Restart service on production
                }
            }
        }
    }

    post {
        always {
            // Archive artifacts after each build (optional)
            archiveArtifacts artifacts: '**/target/*.war'
            // Clean workspace after each build (optional)
            cleanWs()
        }
        success {
            // Send notification on successful build (optional)
            emailext body: 'Build Successful!', subject: 'Job Name - Build # ${BUILD_NUMBER}', to: 'your-email@example.com'
        }
        failure {
            // Send notification on build failure (optional)
            emailext body: 'Build Failed!', subject: 'Job Name - Build # ${BUILD_NUMBER} Failed!', to: 'your-email@example.com'
        }
    }
}

Enter fullscreen mode Exit fullscreen mode
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .