{"id":51507973,"url":"https://github.com/explicit-logic/terraform-module-12.5","last_synced_at":"2026-07-08T02:30:44.829Z","repository":{"id":353435744,"uuid":"1218442194","full_name":"explicit-logic/terraform-module-12.5","owner":"explicit-logic","description":"Complete CI/CD with Terraform","archived":false,"fork":false,"pushed_at":"2026-04-23T22:04:58.000Z","size":10551,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-23T23:24:11.003Z","etag":null,"topics":["aws","cicd","devops-bootcamp","docker","docker-compose","docker-hub","git","java","jenkins","maven","terraform"],"latest_commit_sha":null,"homepage":"","language":"HCL","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/explicit-logic.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-22T22:08:06.000Z","updated_at":"2026-04-23T22:11:29.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/explicit-logic/terraform-module-12.5","commit_stats":null,"previous_names":["explicit-logic/terraform-module-12.5"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/explicit-logic/terraform-module-12.5","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/explicit-logic%2Fterraform-module-12.5","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/explicit-logic%2Fterraform-module-12.5/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/explicit-logic%2Fterraform-module-12.5/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/explicit-logic%2Fterraform-module-12.5/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/explicit-logic","download_url":"https://codeload.github.com/explicit-logic/terraform-module-12.5/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/explicit-logic%2Fterraform-module-12.5/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35249883,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-08T02:00:06.796Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aws","cicd","devops-bootcamp","docker","docker-compose","docker-hub","git","java","jenkins","maven","terraform"],"created_at":"2026-07-08T02:30:44.330Z","updated_at":"2026-07-08T02:30:44.824Z","avatar_url":"https://github.com/explicit-logic.png","language":"HCL","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Module 12 - Infrastructure as Code with Terraform\n\nThis repository contains a demo project created as part of my **DevOps studies** in the [TechWorld with Nana – DevOps Bootcamp](https://www.techworld-with-nana.com/devops-bootcamp).\n\n**Demo Project:** Complete CI/CD with Terraform\n\n**Technologies used:** Terraform, Jenkins, Docker, AWS, Git, Java, Maven, Linux, Docker Hub\n\n**Project Description:**\n\nIntegrate provisioning stage into complete CI/CD Pipeline to automate provisioning server instead of deploying to an existing server.\n\n- Create SSH Key Pair\n- Install Terraform inside Jenkins container\n- Add Terraform configuration to application’s git repository\n- Adjust Jenkinsfile to add “provision” step to the CI/CD pipeline that provisions EC2 instance\n- So the complete CI/CD project we build has the following configuration:\n  - a.CI step: Build artifact for Java Maven application\n  - b.CI step: Build and push Docker image to Docker Hub\n  - c.CD step: Automatically provision EC2 instance using TF\n  - d.CD step: Deploy new application version on the provisioned EC2 instance with Docker Compose\n\n---\n\n### Prerequisites\n\nBefore starting, complete the following setup modules:\n\n- **Jenkins on DigitalOcean:** [jenkins-module-8.1](https://github.com/explicit-logic/jenkins-module-8.1)\n- **Build Tools (Maven, Node):** [jenkins-module-8.2](https://github.com/explicit-logic/jenkins-module-8.2?tab=readme-ov-file#install-build-tools-maven-node-in-jenkins)\n\nIn addition, install the following Jenkins plugin:\n\n- **SSH Agent** — required to forward SSH credentials from Jenkins to the provisioned EC2 instance during the deploy stage.\n\n---\n\n### Configure a Multibranch Pipeline in Jenkins\n\nA multibranch pipeline automatically discovers branches in the repository and runs the `Jenkinsfile` from each one, so feature branches get their own isolated builds.\n\n1. Go to **Dashboard** → **New Item**\n2. Name it `java-maven-app`, select **Multibranch Pipeline**, and click **OK**\n\n**Branch Sources**\n\nClick **Add source** → **GitHub** and fill in:\n\n| Field                | Value                                                        |\n| -------------------- | ------------------------------------------------------------ |\n| Credentials          | `github`                                                     |\n| Repository HTTPS URL | `https://github.com/explicit-logic/terraform-module-12.5`    |\n\nClick **Validate** to confirm that Jenkins can reach the repository.\n\n**Behaviors** — click **Add** and enable:\n\n- `Discover branches`\n\n**Build Configuration**\n\n- Script Path: `Jenkinsfile`\n\n3. Click **Save**. Jenkins scans the repository and creates a job for every branch that contains a `Jenkinsfile`.\n\n---\n\n### Create an SSH Key Pair\n\nThis key pair is used by Jenkins to connect to the EC2 instance that Terraform will provision.\n\nIn the AWS Console, go to **EC2** → **Key pairs** → **Create key pair**:\n\n- **Name:** `myapp-key-pair`\n- **Key pair type:** `RSA`\n- **Private key file format:** `.pem`\n\nThe private key (`myapp-key-pair.pem`) is downloaded automatically — keep it safe, AWS does not let you download it again.\n\n![Create key pair](./images/create-key-pair.png)\n\nRestrict access to the private key so that SSH clients will accept it:\n\n```sh\nchmod 400 ~/Downloads/myapp-key-pair.pem\n```\n\n#### Store the SSH Key Pair in Jenkins\n\nGo to `java-maven-app` → **Credentials** → **Global** → **Add Credentials** and fill in:\n\n| Field       | Value                              |\n| ----------- | ---------------------------------- |\n| Kind        | `SSH Username with private key`    |\n| ID          | `server-ssh-key`                   |\n| Username    | `ec2-user`                         |\n| Private Key | **Enter directly** — paste the contents of the `.pem` file |\n\nCopy the key contents to the clipboard:\n\n```sh\ncat ~/Downloads/myapp-key-pair.pem\n```\n\n![Add SSH key to Jenkins](./images/add-ssh-key-to-jenkins.png)\n\n---\n\n### Add AWS Credentials to Jenkins\n\nTerraform needs AWS access keys to create resources on your behalf. Store them in Jenkins as **Secret text** credentials so they can be injected into the build environment without being exposed in the pipeline logs.\n\n1. Go to `java-maven-app` → **Credentials** → **Global** → **Add Credentials**\n2. Add two **Secret text** credentials using the access keys of an IAM user with the required permissions:\n\n| ID                      | Secret                |\n| ----------------------- | --------------------- |\n| `AWS_ACCESS_KEY_ID`     | `\u003cAccess key\u003e`        |\n| `AWS_SECRET_ACCESS_KEY` | `\u003cSecret access key\u003e` |\n\n![Jenkins AWS credentials](./images/jenkins-aws-creds.png)\n\n---\n\n### Install Terraform inside the Jenkins container\n\nTerraform runs as part of the Jenkins pipeline, so the binary must be available inside the Jenkins container.\n\nConnect to the Jenkins droplet:\n\n```sh\nssh root@\u003cDROPLET-IP\u003e\n```\n\nEnter the Jenkins container as `root`:\n\n```sh\ndocker ps\ndocker exec -it -u 0 \u003ccontainer_id\u003e bash\n```\n\nConfirm the container's OS and version — the installation commands below assume a Debian-based image:\n\n```sh\ncat /etc/os-release\n```\n\n![OS release](./images/os-release.png)\n\nInstall the tools required by the HashiCorp apt repository:\n\n```sh\napt update\napt install -y wget gnupg lsb-release\n\n# Verify installation\nwget --version\ngpg --version\n```\n\nAdd the HashiCorp GPG key and apt repository, then install Terraform (see the [official instructions](https://developer.hashicorp.com/terraform/install) for alternative platforms):\n\n```sh\nwget -O - https://apt.releases.hashicorp.com/gpg | gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg\necho \"deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com bookworm main\" | tee /etc/apt/sources.list.d/hashicorp.list\napt update \u0026\u0026 apt install -y terraform\n\n# Verify installation\nterraform -v\n```\n\n![Install Terraform](./images/install-terraform.png)\n\n---\n\n### Add Terraform configuration to the application repository\n\nCreate a `terraform/` directory at the root of the application repository and add the following files:\n\n- `main.tf` — VPC, subnet, security group, and EC2 instance definitions\n- `variables.tf` — input variables with sensible defaults\n- `entry-script.sh` — user-data script that installs Docker and Docker Compose on the EC2 instance at boot time\n\nRefer to the [Docker Compose standalone installation docs](https://docs.docker.com/compose/install/standalone/) when writing `entry-script.sh`.\n\nTo restrict SSH access to your workstation only, set the `my_ip` variable in `variables.tf` to your current public IP:\n\n```sh\ncurl https://ipinfo.io/ip\n```\n\n---\n\n### Add a “provision server” stage to the Jenkinsfile\n\nThis stage runs `terraform init` and `terraform apply` from inside the pipeline, using the AWS credentials stored in Jenkins.\n\n```groovy\nstage(\"provision server\") {\n  environment {\n    AWS_ACCESS_KEY_ID     = credentials('AWS_ACCESS_KEY_ID')\n    AWS_SECRET_ACCESS_KEY = credentials('AWS_SECRET_ACCESS_KEY')\n    TF_VAR_env_prefix     = 'test'\n  }\n  steps {\n    script {\n      dir('terraform') {\n        sh \"terraform init\"\n        sh \"terraform apply --auto-approve\"\n      }\n    }\n  }\n}\n```\n\n\u003e Variables prefixed with `TF_VAR_` are picked up automatically by Terraform as input variables — here `TF_VAR_env_prefix` sets `var.env_prefix`.\n\nSee the full pipeline: [Jenkinsfile](./Jenkinsfile)\n\n---\n\n### Add the deploy stage to the Jenkinsfile\n\nAfter Terraform finishes, read the EC2 public IP from the Terraform output and expose it as a pipeline variable. Add this at the end of the `provision server` stage:\n\n```groovy\nEC2_PUBLIC_IP = sh(\n  script: \"terraform output ec2_public_ip\",\n  returnStdout: true\n).trim()\n```\n\nThen add the deploy stage, which copies the deployment scripts to the new instance and runs them over SSH:\n\n```groovy\nstage(\"deploy\") {\n  steps {\n    script {\n      echo \"waiting for EC2 server to initialize\"\n      sleep(time: 90, unit: \"SECONDS\")\n\n      echo \"deploying docker image to EC2...\"\n      echo \"${EC2_PUBLIC_IP}\"\n\n      def shellCmd   = \"bash ./server-cmds.sh ${params.IMAGE_NAME}:${params.IMAGE_TAG}\"\n      def ec2Instance = \"ec2-user@${EC2_PUBLIC_IP}\"\n\n      sshagent(['server-ssh-key']) {\n        sh \"scp -o StrictHostKeyChecking=no server-cmds.sh ${ec2Instance}:/home/ec2-user\"\n        sh \"scp -o StrictHostKeyChecking=no docker-compose.yaml ${ec2Instance}:/home/ec2-user\"\n        sh \"ssh -o StrictHostKeyChecking=no ${ec2Instance} ${shellCmd}\"\n      }\n    }\n  }\n}\n```\n\nThe 90-second sleep gives `entry-script.sh` time to finish installing Docker and Docker Compose before the deploy step tries to use them.\n\n---\n\n### Log in to Docker Hub to pull the image\n\nBecause our application image lives in a private Docker Hub repository, the EC2 instance must authenticate before `docker compose up` can pull it. Add the `docker` credentials to the stage environment — Jenkins automatically splits a **Username with password** credential into `DOCKER_CREDS_USR` and `DOCKER_CREDS_PSW`:\n\n```groovy\nenvironment {\n  DOCKER_CREDS = credentials('docker')\n}\n```\n\nThen pass the username and password through to the server script:\n\n```groovy\ndef shellCmd = \"bash ./server-cmds.sh ${params.IMAGE_NAME}:${params.IMAGE_TAG} ${DOCKER_CREDS_USR} ${DOCKER_CREDS_PSW}\"\n```\n\nInside `server-cmds.sh`, use these arguments to run `docker login` on the EC2 instance before pulling the image.\n\n---\n\n### Run the CI/CD pipeline\n\nTrigger a build from Jenkins and wait for all stages to succeed. Once the deploy stage finishes, connect to the freshly provisioned EC2 instance:\n\n```sh\nssh -i ~/Downloads/myapp-key-pair.pem ec2-user@\u003cec2_public_ip\u003e\n```\n\nVerify that the application container is running:\n\n```sh\ndocker ps\n```\n\n**Demo**\n\n![Pipeline demo](./images/demo.gif)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fexplicit-logic%2Fterraform-module-12.5","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fexplicit-logic%2Fterraform-module-12.5","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fexplicit-logic%2Fterraform-module-12.5/lists"}