Building a DevOps Automated Deployment Pipeline using Flask, Aptible, CircleCI, and Prometheus

Written by emmadev | Published 2024/01/30
Tech Story Tags: devops | devops-automated-deployment | automated-development-pipeline | flask-guide | aptible-guide | circleci-guide | prometheus-guide | automated-deployment-strategy

TLDRLearn how to create a powerful DevOps Automated Deployment Pipeline using Flask, Aptible, CircleCI, and Prometheus.via the TL;DR App

Building an automated deployment pipeline is crucial when working on large, scalable applications, it absolutely ensures streamlined automated processes when deploying codes.

In this tutorial guide, we will learn how to build and test a DevOps Automated Deployment Pipeline using Flask, Aptible, CircleCI, and Prometheus for an efficient deployment pipeline.

Prerequisites:

To efficiently build an automated deployment pipeline using Flask, Aptible, CircleCI and Prometheus, the following prerequisites are needed:

  • Flask: An efficient python framework that allows scaffolding python apps into a scalable structure. It is mostly used in creating web applications.
  • Aptible: A well secured and trusted platform for deploying and managing applications.
  • CircleCI: It is used occasionally for building automated pipeline from testing to deployment
  • Prometheus: An open source monitoring and alerting toolkit designed for optimizing research data on the web.

Create a python environment:

Open your terminal to create a new project folder for your application and also a .venv folder within:

> mkdir flask-app
> cd flask-app
> python3 -m venv .venv

Activate the environment:

Activate your flask environment using this command below:

> .venv\Scripts\activate

After running that command your terminal changes to show the name of the activated environment.

Activating a virtual environment in most cases when working on python projects or frameworks, is important for isolating your project's dependencies and ensuring that the environment runs in a consistent pattern.

Install Flask

In that activated environment install flask using the following command:

pip install flask

Now our flask is installed in our python environment.

Note: This tutorial assumes that you already have pip installed on your local machine.

Create an app directory for your Flask app:

In that activated environment, create an app directory.

mkdir app

The app directory will serve as the main directory for your Flask application.

Navigate to the app directory:

cd app

Now we have navigated to our app directory, we need to create this files below:

  • Create an __init__.py file in your app directory.
  • Create an automate.py file in your app directory.

Now, in the __init__.py file follow the code instructions below:

1. Import Flask:

from flask import Flask

This line imports the Flask class from the Flask module.

2. Create a Flask App Instance:

app = Flask(__name__)

Here in this code the instance of the flask class is created.

3. Define a Route:

@app.route('/')
def default_route():
    return 'Automated Deployment'

This code defines a route for the root URL /. when you load your browser the default URL is what is been created.

4. Run the Application:

if __name__ == '__main__':
    app.run(debug=True) 

This conditional statement automatically checks whether the script is being executed directly.

5. Entire Code:

# app/__init__.py

from flask import Flask


app = Flask(__name__)

@app.route('/')
def default_route():
    return 'Automated Deployment'

if __name__ == '__main__':
    app.run(debug=True)

Now, in the automate.py file copy and paste the code below:


# Simple Automation: Addition Function

# Define a function named 'add'
def add(number_One, number_Two):
    # Calculate the sum of the two numbers
    result = number_One + number_Two
    # Return the result
    return result

result = add(3, 3)
print("The sum is:", result)
 

The automate.py code explains a simple arithmetic function that add two different numbers.

Now that you have successfully create automate.py function follow the instructions below to import it into the __init__.py file:

  • import the automate.py function:

    from .automate import add
    

    This import the automate.py function into the __init__.py file.

  • Define the route:

    @app.route('/addition')
    def addition():
        # Call the add function
        sum_result = add(5, 3)
        return f'The sum of the Automated Deployment is: {sum_result}'
    

    This code defines a route for the automate function /addition so that it can be accessible on the browser.

  • Entire code:

    # app/__init__.py
    
    from flask import Flask
    from .automate import add
    
    app = Flask(__name__)
    
    
    @app.route('/addition')
    def addition():
        # Call the add function
        sum_result = add(5, 3)
        return f'The sum of the Automated Deployment is: {sum_result}'
    
    @app.route('/')
    def default_route():
        return 'Automated Deployment'
    
    if __name__ == '__main__':
        app.run(debug=True)
    
    

Set Up Your App Entry Point:

Now, create a run.py in the root directory of our projects and paste this code below:

# run.py

from app import app

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

This run.py file serves as the entry point for running your Flask application. It imports the app instance from the app package and, when executed directly, launches the Flask development server with the specified host port.

  • Start the application server:

Still on the activated environment, run this command on your terminal to start the server:

python run.py

Our app is now running locally..

Copy and paste that url - http://127.0.0.1:5000 on your browser:

Check your browser and see what’s the output..

Set Up Test for Flask Application:

Still on the activated environment install pytest using the following command:

pip install -U pytest

Now our pytest is installed in our python environment.

Create a tests directory for your Flask app:

In that activated environment, create a tests directory.

mkdir tests

The tests directory will serve as our tests for our entire application.

Navigate to the tests directory:

cd tests

  • Create a __init__.py file in your app directory.
  • Create a test_app.py file in your app directory.

Now, in the __init__.py paste this highlighted code:

# tests/__init__.py

# You can put initialization code here if needed.
# This file can also define what should be imported when the package is imported.

The __init__.py file is used to indicate that the directory should be considered a Python package.

Now, in the test_app.py follow the code instructions below:

1. Import Flask App Instance:

from run import app

This line imports the Flask application instance app from the run module.

2. Import automate Module:

from app import automate

This line imports the automate module from the app package.

3. Define a Test Function:

def test_add():
    pass

This code defines a test function named test_add. The pass statement is a placeholder, indicating that this test doesn't have any actual testing logic yet.

4. Entire Code:

from run import app
from app import automate

def test_add():
  pass

5. Run The Tests:

pytest test_app.py

Now our test ran successfully..

Setup Prometheus for metric Analysis:

Still on the activated environment install prometheus using the following command:

pip install prometheus-flask-exporter

Prometheus is now successfully installed on our python environment.

Integrate Prometheus Into Our Flask Application:

To integrate prometheus into our flask application, we need to update our Flask application (__init__.py) to include Prometheus metrics:

# app/__init__.py

from flask import Flask
from prometheus_flask_exporter import PrometheusMetrics
from .automate import add

app = Flask(__name__)
metrics = PrometheusMetrics(app)

@app.route('/addition')
def addition():
    # Call the add function
    sum_result = add(5, 3)
    return f'The sum of the Automated Deployment is: {sum_result}'

@app.route('/')
def default_route():
    return 'Automated Deployment'

if __name__ == '__main__':
    app.run(debug=True)

Test the metrics Analysis:

To see the metrics navigate to this URL /metrics you will see a response like this on the browser as a metrics:

# HELP python_gc_objects_collected_total Objects collected during gc
# TYPE python_gc_objects_collected_total counter
python_gc_objects_collected_total{generation="0"} 232.0
python_gc_objects_collected_total{generation="1"} 71.0
python_gc_objects_collected_total{generation="2"} 0.0
# HELP python_gc_objects_uncollectable_total Uncollectable objects found during GC
# TYPE python_gc_objects_uncollectable_total counter
python_gc_objects_uncollectable_total{generation="0"} 0.0
python_gc_objects_uncollectable_total{generation="1"} 0.0
python_gc_objects_uncollectable_total{generation="2"} 0.0
# HELP python_gc_collections_total Number of times this generation was collected
# TYPE python_gc_collections_total counter
python_gc_collections_total{generation="0"} 68.0
python_gc_collections_total{generation="1"} 6.0
python_gc_collections_total{generation="2"} 0.0
# HELP python_info Python platform information
# TYPE python_info gauge
python_info{implementation="CPython",major="3",minor="12",patchlevel="0",version="3.12.0"} 1.0
# HELP flask_exporter_info Information about the Prometheus Flask exporter
# TYPE flask_exporter_info gauge
flask_exporter_info{version="0.23.0"} 1.0
# HELP flask_http_request_duration_seconds Flask HTTP request duration in seconds
# TYPE flask_http_request_duration_seconds histogram
flask_http_request_duration_seconds_bucket{le="0.005",method="GET",path="/",status="200"} 1.0
flask_http_request_duration_seconds_bucket{le="0.01",method="GET",path="/",status="200"} 1.0
flask_http_request_duration_seconds_bucket{le="0.025",method="GET",path="/",status="200"} 1.0
flask_http_request_duration_seconds_bucket{le="0.05",method="GET",path="/",status="200"} 1.0
flask_http_request_duration_seconds_bucket{le="0.075",method="GET",path="/",status="200"} 1.0
flask_http_request_duration_seconds_bucket{le="0.1",method="GET",path="/",status="200"} 1.0
flask_http_request_duration_seconds_bucket{le="0.25",method="GET",path="/",status="200"} 1.0
flask_http_request_duration_seconds_bucket{le="0.5",method="GET",path="/",status="200"} 1.0
flask_http_request_duration_seconds_bucket{le="0.75",method="GET",path="/",status="200"} 1.0
flask_http_request_duration_seconds_bucket{le="1.0",method="GET",path="/",status="200"} 1.0
flask_http_request_duration_seconds_bucket{le="2.5",method="GET",path="/",status="200"} 1.0
flask_http_request_duration_seconds_bucket{le="5.0",method="GET",path="/",status="200"} 1.0
flask_http_request_duration_seconds_bucket{le="7.5",method="GET",path="/",status="200"} 1.0
flask_http_request_duration_seconds_bucket{le="10.0",method="GET",path="/",status="200"} 1.0
flask_http_request_duration_seconds_bucket{le="+Inf",method="GET",path="/",status="200"} 1.0
flask_http_request_duration_seconds_count{method="GET",path="/",status="200"} 1.0
flask_http_request_duration_seconds_sum{method="GET",path="/",status="200"} 0.00026679993607103825
flask_http_request_duration_seconds_bucket{le="0.005",method="GET",path="/favicon.ico",status="404"} 1.0
flask_http_request_duration_seconds_bucket{le="0.01",method="GET",path="/favicon.ico",status="404"} 1.0
flask_http_request_duration_seconds_bucket{le="0.025",method="GET",path="/favicon.ico",status="404"} 1.0
flask_http_request_duration_seconds_bucket{le="0.05",method="GET",path="/favicon.ico",status="404"} 1.0
flask_http_request_duration_seconds_bucket{le="0.075",method="GET",path="/favicon.ico",status="404"} 1.0
flask_http_request_duration_seconds_bucket{le="0.1",method="GET",path="/favicon.ico",status="404"} 1.0
flask_http_request_duration_seconds_bucket{le="0.25",method="GET",path="/favicon.ico",status="404"} 1.0
flask_http_request_duration_seconds_bucket{le="0.5",method="GET",path="/favicon.ico",status="404"} 1.0
flask_http_request_duration_seconds_bucket{le="0.75",method="GET",path="/favicon.ico",status="404"} 1.0
flask_http_request_duration_seconds_bucket{le="1.0",method="GET",path="/favicon.ico",status="404"} 1.0
flask_http_request_duration_seconds_bucket{le="2.5",method="GET",path="/favicon.ico",status="404"} 1.0
flask_http_request_duration_seconds_bucket{le="5.0",method="GET",path="/favicon.ico",status="404"} 1.0
flask_http_request_duration_seconds_bucket{le="7.5",method="GET",path="/favicon.ico",status="404"} 1.0
flask_http_request_duration_seconds_bucket{le="10.0",method="GET",path="/favicon.ico",status="404"} 1.0
flask_http_request_duration_seconds_bucket{le="+Inf",method="GET",path="/favicon.ico",status="404"} 1.0
flask_http_request_duration_seconds_count{method="GET",path="/favicon.ico",status="404"} 1.0
flask_http_request_duration_seconds_sum{method="GET",path="/favicon.ico",status="404"} 0.0006099999882280827
# HELP flask_http_request_duration_seconds_created Flask HTTP request duration in seconds
# TYPE flask_http_request_duration_seconds_created gauge
flask_http_request_duration_seconds_created{method="GET",path="/",status="200"} 1.706334968886746e+09
flask_http_request_duration_seconds_created{method="GET",path="/favicon.ico",status="404"} 1.7063349712633328e+09
# HELP flask_http_request_total Total number of HTTP requests
# TYPE flask_http_request_total counter
flask_http_request_total{method="GET",status="200"} 1.0
flask_http_request_total{method="GET",status="404"} 1.0
# HELP flask_http_request_created Total number of HTTP requests
# TYPE flask_http_request_created gauge
flask_http_request_created{method="GET",status="200"} 1.706334968886746e+09
flask_http_request_created{method="GET",status="404"} 1.7063349712633328e+09
# HELP flask_http_request_exceptions_total Total number of HTTP requests which resulted in an exception
# TYPE flask_http_request_exceptions_total counter

Setting Up Aptible:

To set up Aptible, sign up for an account and create a new environment for your flask application.

Dockerize Your Flask Application:

  • Here we are to dockerize our flask application for efficient automation pipeline.

Note: In this tutorial, am not running Docker directly from my local machine, it will be configured directly with CircleCI CI/CD(continuous integration and continuous delivery). Ensure you follow every steps below:

  • Create a Dockerfile in the root directory of your Flask application.
# Use the official Alpine image with Python 3.8
FROM python:3.8-alpine

# Set the working directory to /app
WORKDIR /app

# Install dependencies
RUN apk add --no-cache python3 && \
    python3 -m ensurepip && \
    pip3 install --upgrade pip && \
    pip3 install Flask prometheus-flask-exporter

# Copy the current directory contents into the container at /app
COPY . /app

# Expose port 5000
EXPOSE 5000

# Define the command to run your application
CMD ["python3", "run.py"]

Create a circleci directory for your Flask app:

  • Still under that activated environment, create a .circleci folder in your root directory and.
mkdir .circleci

This .circleci directory will serve as a file that will configure and interact CircleCI into our application.

Navigate to the .circleci directory:

cd .circleci

Now we have navigated to our .circleci directory, we need to create this file below:

  • Create a config.yml file in your .circleci directory.

Now, in the config.yml file copy and paste this code snippet below:

version: 2.1

jobs:
  build:
    docker:
      - image: docker:19.03.12

    steps:
      - checkout

      - setup_remote_docker:
          version: 19.03.12
          
      - run:
          name: Build Docker Image
          command: |
            docker build -t flask-app:latest .
            docker images

workflows:
  version: 2
  build_and_deploy:
    jobs:
      - build

Version Control with Git:

Initialize your Git repository for version control and set up branches for development, testing, and production.

git init
git add .
git commit -m "Initial commit"
git branch -M main
git remote add origin githubprojectUrl
git push origin main

Note: This tutorial assumes that you know how to efficiently work with github and you have already setup a github account.

Continuous Integration with CircleCI:

  • Set Up Account For CircleCI:

    Setup a free account on CircleCI and navigate to the projects in the CircleCI dashboard and create a new project.

  • Choose a suitable deploy container for your code:

    Now, when you click on the create project this page automatically appears to choose provider for storing and managing our code. Click on Github to continue. We go ahead and choose Github, which is what we’ll be using through out the tutorial.

  • Connect Github with CircleCI:

To connect with Github using CircleCI follow this steps below:

  • Enter your project name in that form box example: flask-app.
  • Generate an SSH Key:

To generate an SSH key, open your terminal and use this command below:

ssh-keygen -t ed25519 -f ~/.ssh/project_key -C "[email protected]"

This command automatically generate two keys- private and a public key.

To easily navigate to the generated keys use the type command together with the path where the SSH keys are been saved. Copy and paste this command below to see the generated public key:

type "C:\Users\EMMANUEL UMEH\.ssh\project_key.pub"

After you have copy that key go to your Github, navigate to the github project we pushed our code to < Enter Settings < navigate to Deploy keys:

  • Click on Add Deploy Keys.
  • Enter the Title and the public Key you generated from the terminal.
  • Click on Allow write access.
  • Lastly, click on Add Key.

To easily navigate to the generated keys for private key use the type command together with the path where the SSH keys are been saved.

type "C:\Users\EMMANUEL UMEH\.ssh\project_key"

Copy and paste the Key and paste in the CircleCI form box that request for private key.

To not easily get confused, please just go to your local hard drive and locate where the .shh folder is been stored. Open with a code editor either vscode or any code editor and copy the keys. Mostly private keys when you open the code editor are mostly save project_key while public is saved as project_key.pub.

Click on the Repository and finally click on the Create Project to start deploying to CircleCI.

Note: Do not share any of this keys especially your private key this is for security reasons.

After it is done it automatically builds the repository and our project is now on CircleCI. Here is the response it gives when you click on the - Build Docker Image:

Sending build context to Docker daemon  40.45kB
Step 1/6 : FROM python:3.8-alpine
3.8-alpine: Pulling from library/python
661ff4d9561e: Pull complete 
44cda88cd45d: Pull complete 
588652b0a3bf: Pull complete 
a01b0f6dede8: Pull complete 
06be91cd02e1: Pull complete 
Digest: sha256:aeb77f60b4b197c265fc02b305753343da6155b065faa2e60671be83fc830d46
Status: Downloaded newer image for python:3.8-alpine
 ---> 654f089483df
Step 2/6 : WORKDIR /app
 ---> Running in 2a1331abce5a
Removing intermediate container 2a1331abce5a
 ---> 8230e4cf500a
Step 3/6 : RUN apk add --no-cache python3 &&     python3 -m ensurepip &&     pip3 install --upgrade pip &&     pip3 install Flask prometheus-flask-exporter
 ---> Running in 37570b975c16
fetch https://dl-cdn.alpinelinux.org/alpine/v3.19/main/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/v3.19/community/x86_64/APKINDEX.tar.gz
(1/7) Installing libgcc (13.2.1_git20231014-r0)
(2/7) Installing libstdc++ (13.2.1_git20231014-r0)
(3/7) Installing mpdecimal (2.5.1-r2)
(4/7) Installing python3 (3.11.6-r1)
(5/7) Installing python3-pycache-pyc0 (3.11.6-r1)
(6/7) Installing pyc (3.11.6-r1)
(7/7) Installing python3-pyc (3.11.6-r1)
Executing busybox-1.36.1-r15.trigger
OK: 57 MiB in 45 packages
Looking in links: /tmp/tmp_6yka1up
Requirement already satisfied: setuptools in /usr/local/lib/python3.8/site-packages (57.5.0)
Requirement already satisfied: pip in /usr/local/lib/python3.8/site-packages (23.0.1)
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
Requirement already satisfied: pip in /usr/local/lib/python3.8/site-packages (23.0.1)
Collecting pip
  Downloading pip-23.3.2-py3-none-any.whl (2.1 MB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.1/2.1 MB 68.8 MB/s eta 0:00:00
Installing collected packages: pip
  Attempting uninstall: pip
    Found existing installation: pip 23.0.1
    Uninstalling pip-23.0.1:
      Successfully uninstalled pip-23.0.1
Successfully installed pip-23.3.2
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
Collecting Flask
  Downloading flask-3.0.1-py3-none-any.whl.metadata (3.6 kB)
Collecting prometheus-flask-exporter
  Downloading prometheus_flask_exporter-0.23.0-py3-none-any.whl.metadata (19 kB)
Collecting Werkzeug>=3.0.0 (from Flask)
  Downloading werkzeug-3.0.1-py3-none-any.whl.metadata (4.1 kB)
Collecting Jinja2>=3.1.2 (from Flask)
  Downloading Jinja2-3.1.3-py3-none-any.whl.metadata (3.3 kB)
Collecting itsdangerous>=2.1.2 (from Flask)
  Downloading itsdangerous-2.1.2-py3-none-any.whl (15 kB)
Collecting click>=8.1.3 (from Flask)
  Downloading click-8.1.7-py3-none-any.whl.metadata (3.0 kB)
Collecting blinker>=1.6.2 (from Flask)
  Downloading blinker-1.7.0-py3-none-any.whl.metadata (1.9 kB)
Collecting importlib-metadata>=3.6.0 (from Flask)
  Downloading importlib_metadata-7.0.1-py3-none-any.whl.metadata (4.9 kB)
Collecting prometheus-client (from prometheus-flask-exporter)
  Downloading prometheus_client-0.19.0-py3-none-any.whl.metadata (1.8 kB)
Collecting zipp>=0.5 (from importlib-metadata>=3.6.0->Flask)
  Downloading zipp-3.17.0-py3-none-any.whl.metadata (3.7 kB)
Collecting MarkupSafe>=2.0 (from Jinja2>=3.1.2->Flask)
  Downloading MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_x86_64.whl.metadata (3.0 kB)
Downloading flask-3.0.1-py3-none-any.whl (101 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 101.2/101.2 kB 9.0 MB/s eta 0:00:00
Downloading prometheus_flask_exporter-0.23.0-py3-none-any.whl (18 kB)
Downloading blinker-1.7.0-py3-none-any.whl (13 kB)
Downloading click-8.1.7-py3-none-any.whl (97 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 97.9/97.9 kB 8.9 MB/s eta 0:00:00
Downloading importlib_metadata-7.0.1-py3-none-any.whl (23 kB)
Downloading Jinja2-3.1.3-py3-none-any.whl (133 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 133.2/133.2 kB 11.6 MB/s eta 0:00:00
Downloading werkzeug-3.0.1-py3-none-any.whl (226 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 226.7/226.7 kB 18.0 MB/s eta 0:00:00
Downloading prometheus_client-0.19.0-py3-none-any.whl (54 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 54.2/54.2 kB 5.5 MB/s eta 0:00:00
Downloading MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_x86_64.whl (29 kB)
Downloading zipp-3.17.0-py3-none-any.whl (7.4 kB)
Installing collected packages: zipp, prometheus-client, MarkupSafe, itsdangerous, click, blinker, Werkzeug, Jinja2, importlib-metadata, Flask, prometheus-flask-exporter
Successfully installed Flask-3.0.1 Jinja2-3.1.3 MarkupSafe-2.1.4 Werkzeug-3.0.1 blinker-1.7.0 click-8.1.7 importlib-metadata-7.0.1 itsdangerous-2.1.2 prometheus-client-0.19.0 prometheus-flask-exporter-0.23.0 zipp-3.17.0
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
Removing intermediate container 37570b975c16
 ---> 1e7f53ea0501
Step 4/6 : COPY . /app
 ---> c4e65ff9f064
Step 5/6 : EXPOSE 5000
 ---> Running in 8a0e7bf09b92
Removing intermediate container 8a0e7bf09b92
 ---> 21cbafd1e043
Step 6/6 : CMD ["python3", "run.py"]
 ---> Running in 88ea77be74aa
Removing intermediate container 88ea77be74aa
 ---> 93f1e8a979f1
Successfully built 93f1e8a979f1
Successfully tagged flask-app:latest
REPOSITORY                                   TAG                 IMAGE ID            CREATED                  SIZE
flask-app                                    latest              93f1e8a979f1        Less than a second ago   112MB
python                                       3.8-alpine          654f089483df        5 weeks ago              49.7MB
public.ecr.aws/eks-distro/kubernetes/pause   3.6                 3c69a9ca2c95        3 months ago             6.62MB
docker                                       19.03.12            81f5749c9058        3 years ago              211MB

Configure Aptible with CircleCI:

To automatically configure Aptible with CircleCI follow these steps below:

  • Go to the Projects on the dashboard, click on that triple-menu after clicking a modal appears, go ahead and click on Project Settings:

  • After you have clicked on that Project Settings a page appears. Navigate to the SSH Keys page, Add Hostname, in the hostname use the beta.aptible.com as the hostname and add that same private key you use in your CircleCI configuration. Then finally you click on the Add SSH Key.

  • Navigate to the Environment Variables page also to add the Aptible environment. Click on the Add Environment Variable and add an environment variable named - APTIBLE_APP and in the input value box put the name of your Aptible name.

Also add another environment variable named- APTIBLE_ENVIRONMENT and in the input value box put the name of your Aptible environment.

Now all these are successfully setted up, let update our circleci config file to deploy to aptible.

On the config.yml file update the code below:

version: 2.1

jobs:
  build:
    docker:
      - image: docker:19.03.12

    steps:
      - checkout

      - setup_remote_docker:
          version: 19.03.12
          
      - run:
          name: Build Docker Image
          command: |
            docker build -t flask-app:latest .
            docker images

  deploy:
    docker:
      - image: docker:19.03.12

    steps:
      - checkout

      - setup_remote_docker:
          version: 19.03.12

      - run:
          name: Install Git and OpenSSH
          command: |
            apk add --no-cache git openssh-client

      - run:
          name: Configure SSH for Aptible
          command: |
            ssh-keyscan beta.aptible.com >> ~/.ssh/known_hosts
            echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
            chmod 600 ~/.ssh/id_rsa

      - run:
          name: Add Aptible Git Remote
          command: |
            git remote add aptible [email protected]:$APTIBLE_ENVIRONMENT/$APTIBLE_APP.git

      - run:
          name: Push to Aptible
          command: |
            git push aptible $CIRCLE_SHA1:refs/heads/main

workflows:
  version: 2
  build_and_deploy:
    jobs:
      - build
      - deploy:
          requires:
            - build

This code will automatically build and deploy to aptible.

Check your aptible deployment on the dashboard and see that it is connected to the circleci.

This is response that the aptible logs when it is deployed successfully:

17:06:09 INFO: Starting App deploy operation with ID: 63423982
17:06:19 INFO: Deploying with git commit: 1f17ec8d8db188e9a71e2389e1f7bde6c71277fb
17:06:26 INFO: Writing .aptible.env file...
17:06:26 INFO: Building app image from Dockerfile...
17:06:26 INFO: Step 1/6 : FROM python:3.8-alpine
17:06:26 INFO:
17:06:27 INFO: Pulling from library/python
17:06:27 INFO: Digest: sha256:aeb77f60b4b197c265fc02b305753343da6155b065faa2e60671be83fc830d46
17:06:27 INFO: Status: Image is up to date for python:3.8-alpine
17:06:27 INFO: ---> 654f089483df
17:06:27 INFO: Step 2/6 : WORKDIR /app
17:06:27 INFO:
17:06:27 INFO: ---> Using cache
17:06:27 INFO: ---> 5abee9094d97
17:06:27 INFO: Step 3/6 : RUN apk add --no-cache python3 && python3 -m ensurepip && pip3 install --upgrade pip && pip3 install Flask prometheus-flask-exporter
17:06:27 INFO:
17:06:27 INFO: ---> Running in 301e637d03a9
17:06:27 INFO: fetch https://dl-cdn.alpinelinux.org/alpine/v3.19/main/x86_64/APKINDEX.tar.gz
17:06:28 INFO: fetch https://dl-cdn.alpinelinux.org/alpine/v3.19/community/x86_64/APKINDEX.tar.gz
17:06:28 INFO: (1/7) Installing libgcc (13.2.1_git20231014-r0)
17:06:28 INFO: (2/7) Installing libstdc++ (13.2.1_git20231014-r0)
17:06:28 INFO: (3/7) Installing mpdecimal (2.5.1-r2)
17:06:28 INFO: (4/7) Installing python3 (3.11.6-r1)
17:06:28 INFO: (5/7) Installing python3-pycache-pyc0 (3.11.6-r1)
17:06:28 INFO: (6/7) Installing pyc (3.11.6-r1)
17:06:28 INFO: (7/7) Installing python3-pyc (3.11.6-r1)
17:06:28 INFO: Executing busybox-1.36.1-r15.trigger
17:06:28 INFO: OK: 57 MiB in 45 packages
17:06:31 INFO: Looking in links: /tmp/tmpj9vuta0p
17:06:31 INFO: Requirement already satisfied: setuptools in /usr/local/lib/python3.8/site-packages (57.5.0)
17:06:31 INFO: Requirement already satisfied: pip in /usr/local/lib/python3.8/site-packages (23.0.1)
17:06:31 INFO: WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv

17:06:32 INFO: Requirement already satisfied: pip in /usr/local/lib/python3.8/site-packages (23.0.1)
17:06:32 INFO: Collecting pip
17:06:32 INFO: Downloading pip-23.3.2-py3-none-any.whl (2.1 MB)
17:06:32 INFO: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.1/2.1 MB 46.5 MB/s eta 0:00:00
17:06:32 INFO:
17:06:32 INFO: Installing collected packages: pip
17:06:32 INFO: Attempting uninstall: pip
17:06:32 INFO: Found existing installation: pip 23.0.1
17:06:32 INFO: Uninstalling pip-23.0.1:
17:06:33 INFO: Successfully uninstalled pip-23.0.1
17:06:34 INFO: Successfully installed pip-23.3.2
17:06:34 INFO: WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv

17:06:35 INFO: Collecting Flask
17:06:35 INFO: Downloading flask-3.0.1-py3-none-any.whl.metadata (3.6 kB)
17:06:35 INFO: Collecting prometheus-flask-exporter
17:06:35 INFO: Downloading prometheus_flask_exporter-0.23.0-py3-none-any.whl.metadata (19 kB)
17:06:35 INFO: Collecting Werkzeug>=3.0.0 (from Flask)
17:06:35 INFO: Downloading werkzeug-3.0.1-py3-none-any.whl.metadata (4.1 kB)
17:06:35 INFO: Collecting Jinja2>=3.1.2 (from Flask)
17:06:35 INFO: Downloading Jinja2-3.1.3-py3-none-any.whl.metadata (3.3 kB)
17:06:35 INFO: Collecting itsdangerous>=2.1.2 (from Flask)
17:06:35 INFO: Downloading itsdangerous-2.1.2-py3-none-any.whl (15 kB)
17:06:35 INFO: Collecting click>=8.1.3 (from Flask)
17:06:35 INFO: Downloading click-8.1.7-py3-none-any.whl.metadata (3.0 kB)
17:06:35 INFO: Collecting blinker>=1.6.2 (from Flask)
17:06:35 INFO: Downloading blinker-1.7.0-py3-none-any.whl.metadata (1.9 kB)
17:06:35 INFO: Collecting importlib-metadata>=3.6.0 (from Flask)
17:06:35 INFO: Downloading importlib_metadata-7.0.1-py3-none-any.whl.metadata (4.9 kB)
17:06:35 INFO: Collecting prometheus-client (from prometheus-flask-exporter)
17:06:35 INFO: Downloading prometheus_client-0.19.0-py3-none-any.whl.metadata (1.8 kB)
17:06:35 INFO: Collecting zipp>=0.5 (from importlib-metadata>=3.6.0->Flask)
17:06:35 INFO: Downloading zipp-3.17.0-py3-none-any.whl.metadata (3.7 kB)
17:06:35 INFO: Collecting MarkupSafe>=2.0 (from Jinja2>=3.1.2->Flask)
17:06:35 INFO: Downloading MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_x86_64.whl.metadata (3.0 kB)
17:06:35 INFO: Downloading flask-3.0.1-py3-none-any.whl (101 kB)
17:06:35 INFO: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 101.2/101.2 kB 18.3 MB/s eta 0:00:00
17:06:35 INFO:
17:06:35 INFO: Downloading prometheus_flask_exporter-0.23.0-py3-none-any.whl (18 kB)
17:06:35 INFO: Downloading blinker-1.7.0-py3-none-any.whl (13 kB)
17:06:35 INFO: Downloading click-8.1.7-py3-none-any.whl (97 kB)
17:06:35 INFO: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 97.9/97.9 kB 14.8 MB/s eta 0:00:00
17:06:35 INFO:
17:06:35 INFO: Downloading importlib_metadata-7.0.1-py3-none-any.whl (23 kB)
17:06:35 INFO: Downloading Jinja2-3.1.3-py3-none-any.whl (133 kB)
17:06:35 INFO: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 133.2/133.2 kB 24.7 MB/s eta 0:00:00
17:06:35 INFO: Downloading werkzeug-3.0.1-py3-none-any.whl (226 kB)
17:06:35 INFO: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 226.7/226.7 kB 33.1 MB/s eta 0:00:00
17:06:35 INFO:
17:06:35 INFO: Downloading prometheus_client-0.19.0-py3-none-any.whl (54 kB)
17:06:35 INFO: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 54.2/54.2 kB 8.8 MB/s eta 0:00:00
17:06:35 INFO:
17:06:35 INFO: Downloading MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_x86_64.whl (29 kB)
17:06:35 INFO: Downloading zipp-3.17.0-py3-none-any.whl (7.4 kB)
17:06:35 INFO: Installing collected packages: zipp, prometheus-client, MarkupSafe, itsdangerous, click, blinker, Werkzeug, Jinja2, importlib-metadata, Flask, prometheus-flask-exporter
17:06:36 INFO: Successfully installed Flask-3.0.1 Jinja2-3.1.3 MarkupSafe-2.1.4 Werkzeug-3.0.1 blinker-1.7.0 click-8.1.7 importlib-metadata-7.0.1 itsdangerous-2.1.2 prometheus-client-0.19.0 prometheus-flask-exporter-0.23.0 zipp-3.17.0
17:06:36 INFO: WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv

17:06:37 INFO: Removing intermediate container 301e637d03a9
17:06:37 INFO: ---> b7da707a0f81
17:06:37 INFO: Step 4/6 : COPY . /app
17:06:37 INFO:
17:06:38 INFO: ---> 49d87db774f2
17:06:38 INFO: Step 5/6 : EXPOSE 5000
17:06:38 INFO:
17:06:38 INFO: ---> Running in 5d70b7d7f835
17:06:38 INFO: Removing intermediate container 5d70b7d7f835
17:06:38 INFO: ---> 4773ef4d4e6e
17:06:38 INFO: Step 6/6 : CMD ["python3", "run.py"]
17:06:38 INFO:
17:06:38 INFO: ---> Running in 17ab74c4f3ba
17:06:38 INFO: Removing intermediate container 17ab74c4f3ba
17:06:38 INFO: ---> 9a93b7aeb34a
17:06:38 INFO: Successfully built 9a93b7aeb34a
17:06:38 INFO: No Procfile found in git directory or /.aptible/Procfile found in Docker image: using Docker image CMD
17:06:38 INFO: No .aptible.yml found in git directory or /.aptible/.aptible.yml found in Docker image: no before_release commands will run
17:06:38 INFO: Pushing image dualstack-v2-registry-i-0a95e4f42c277b7d6.aptible.in:46022/app-67132/7edac14d-6667-4cdb-a6e8-fcd6aea9cb4d:latest to private Docker registry...
17:06:38 INFO: The push refers to repository [dualstack-v2-registry-i-0a95e4f42c277b7d6.aptible.in:46022/app-67132/7edac14d-6667-4cdb-a6e8-fcd6aea9cb4d]
17:06:38 INFO: 2014143cee52: Pushing: 521 KB / 59.3 MB
17:06:39 INFO: e6035e0a4780: Pushed
17:06:40 INFO: 2014143cee52: Pushing: 30.6 MB / 59.3 MB
17:06:44 INFO: 2014143cee52: Pushed
17:06:45 INFO: latest: digest: sha256:70e44dc2f539188b8e05952db0283e646a17103a6d29b876f25ec7a368f053d5 size: 1995
17:06:45 INFO: Pulling from app-67132/7edac14d-6667-4cdb-a6e8-fcd6aea9cb4d
17:06:45 INFO: Digest: sha256:70e44dc2f539188b8e05952db0283e646a17103a6d29b876f25ec7a368f053d5
17:06:45 INFO: Status: Image is up to date for dualstack-v2-registry-i-0a95e4f42c277b7d6.aptible.in:46022/app-67132/7edac14d-6667-4cdb-a6e8-fcd6aea9cb4d:latest
17:06:45 INFO: Image app-67132/7edac14d-6667-4cdb-a6e8-fcd6aea9cb4d successfully pushed to registry.
17:06:47 INFO: STARTING: Register service cmd in API
17:06:47 INFO: COMPLETED (after 0.0s): Register service cmd in API
17:06:47 INFO: STARTING: Extract configuration for endpoint app-67132.on-aptible.com
17:06:47 INFO: COMPLETED (after 0.0s): Extract configuration for endpoint app-67132.on-aptible.com
17:06:47 INFO: STARTING: Import certificate for endpoint app-67132.on-aptible.com
17:06:48 INFO: COMPLETED (after 0.0s): Import certificate for endpoint app-67132.on-aptible.com
17:06:48 INFO: STARTING: Derive placement policy for service cmd
17:06:48 INFO: COMPLETED (after 0.13s): Derive placement policy for service cmd
17:06:48 INFO: STARTING: Create new release for service cmd
17:06:48 INFO: COMPLETED (after 0.26s): Create new release for service cmd
17:06:48 INFO: STARTING: Ensure ALB exists for endpoint app-67132.on-aptible.com
17:06:48 INFO: COMPLETED (after 0.05s): Ensure ALB exists for endpoint app-67132.on-aptible.com
17:06:48 INFO: STARTING: Schedule service cmd
17:07:02 INFO: COMPLETED (after 14.11s): Schedule service cmd
17:07:02 INFO: STARTING: Start app containers for service cmd
17:07:03 INFO: STARTING: Configure IP filtering for endpoint app-67132.on-aptible.com
17:07:03 INFO: COMPLETED (after 0.04s): Configure IP filtering for endpoint app-67132.on-aptible.com
17:07:03 INFO: STARTING: Configure SSL Certificate for endpoint app-67132.on-aptible.com
17:07:03 INFO: COMPLETED (after 0.05s): Configure SSL Certificate for endpoint app-67132.on-aptible.com
17:07:03 INFO: STARTING: Configure SSL Policy for endpoint app-67132.on-aptible.com
17:07:03 INFO: COMPLETED (after 0.06s): Configure SSL Policy for endpoint app-67132.on-aptible.com
17:07:03 INFO: WAITING FOR: Start app containers for service cmd
17:07:05 INFO: COMPLETED (after 2.65s): Start app containers for service cmd
17:07:05 INFO: STARTING: Wait up to 180s for containers in service cmd to respond to HTTP health checks
17:07:05 INFO: STARTING: Start proxy containers for endpoint app-67132.on-aptible.com
17:07:05 INFO: WAITING FOR: Start proxy containers for endpoint app-67132.on-aptible.com, Wait up to 180s for containers in service cmd to respond to HTTP health checks
17:07:08 INFO: COMPLETED (after 2.54s): Wait up to 180s for containers in service cmd to respond to HTTP health checks
17:07:08 INFO: WAITING FOR: Start proxy containers for endpoint app-67132.on-aptible.com
17:07:12 INFO: COMPLETED (after 6.16s): Start proxy containers for endpoint app-67132.on-aptible.com
17:07:12 INFO: STARTING: Register new http targets with endpoint app-67132.on-aptible.com
17:07:12 INFO: STARTING: Register new https targets with endpoint app-67132.on-aptible.com
17:07:12 INFO: WAITING FOR: Register new http targets with endpoint app-67132.on-aptible.com, Register new https targets with endpoint app-67132.on-aptible.com
17:07:24 INFO: COMPLETED (after 12.92s): Register new http targets with endpoint app-67132.on-aptible.com
17:07:25 INFO: COMPLETED (after 12.74s): Register new https targets with endpoint app-67132.on-aptible.com
17:07:25 INFO: STARTING: Deregister old http targets with endpoint app-67132.on-aptible.com
17:07:25 INFO: STARTING: Deregister old https targets with endpoint app-67132.on-aptible.com
17:07:25 INFO: WAITING FOR: Deregister old http targets with endpoint app-67132.on-aptible.com, Deregister old https targets with endpoint app-67132.on-aptible.com
17:07:37 INFO: WAITING FOR: Deregister old http targets with endpoint app-67132.on-aptible.com, Deregister old https targets with endpoint app-67132.on-aptible.com
17:07:41 INFO: COMPLETED (after 16.9s): Deregister old http targets with endpoint app-67132.on-aptible.com
17:07:42 INFO: COMPLETED (after 16.78s): Deregister old https targets with endpoint app-67132.on-aptible.com
17:07:42 INFO: STARTING: Wait for Route 53 health to sync for endpoint app-67132.on-aptible.com
17:07:42 INFO: COMPLETED (after 0.0s): Wait for Route 53 health to sync for endpoint app-67132.on-aptible.com
17:07:42 INFO: STARTING: Create ALIAS for endpoint app-67132.on-aptible.com
17:07:42 INFO: COMPLETED (after 0.56s): Create ALIAS for endpoint app-67132.on-aptible.com
17:07:42 INFO: STARTING: Create default domain for endpoint app-67132.on-aptible.com
17:07:42 INFO: COMPLETED (after 0.32s): Create default domain for endpoint app-67132.on-aptible.com
17:07:42 INFO: STARTING: Wait for DNS to sync for endpoint app-67132.on-aptible.com
17:07:42 INFO: COMPLETED (after 0.0s): Wait for DNS to sync for endpoint app-67132.on-aptible.com
17:07:42 INFO: STARTING: Stop old proxy containers for endpoint app-67132.on-aptible.com
17:07:42 INFO: WAITING FOR: Stop old proxy containers for endpoint app-67132.on-aptible.com
17:07:44 INFO: COMPLETED (after 2.01s): Stop old proxy containers for endpoint app-67132.on-aptible.com
17:07:44 INFO: STARTING: Stop old app containers for service cmd
17:07:44 INFO: WAITING FOR: Stop old app containers for service cmd
17:07:55 INFO: WAITING FOR: Stop old app containers for service cmd
17:07:57 INFO: COMPLETED (after 12.24s): Stop old app containers for service cmd
17:07:57 INFO: STARTING: Delete old containers for service cmd in API
17:07:57 INFO: COMPLETED (after 0.22s): Delete old containers for service cmd in API
17:07:57 INFO: STARTING: Commit proxy containers in API for endpoint app-67132.on-aptible.com
17:07:57 INFO: COMPLETED (after 0.27s): Commit proxy containers in API for endpoint app-67132.on-aptible.com
17:07:57 INFO: STARTING: Delete old proxy containers for endpoint app-67132.on-aptible.com in API
17:07:58 INFO: COMPLETED (after 0.2s): Delete old proxy containers for endpoint app-67132.on-aptible.com in API
17:07:58 INFO: STARTING: Commit app containers in API for service cmd
17:07:58 INFO: COMPLETED (after 0.27s): Commit app containers in API for service cmd
17:07:58 INFO: STARTING: Commit service cmd in API
17:07:58 INFO: COMPLETED (after 0.16s): Commit service cmd in API
17:07:58 INFO: STARTING: Cache maintenance page
17:07:59 INFO: COMPLETED (after 0.3s): Cache maintenance page
17:07:59 INFO: STARTING: Commit app in API
17:07:59 INFO: COMPLETED (after 0.18s): Commit app in API
17:08:00 INFO: App deploy successful.

Conclusion

Automating deployment pipeline using DevOps best practices ensures a streamlined process for a well secure and robust software application.


Written by emmadev | Technical Writer and Software developer.
Published by HackerNoon on 2024/01/30