Automating the deployment of your development environment using Okteto Actions and GitHub Actions.

Nwani Victory works remotely as a software engineer, building scalable and sustainable software. Outside working hours, he doubles as a technical writer, creating technical articles focused on modern web technologies and public cloud providers.
Introduction
GitHub Actions provides developers with the functionality of building and automating the execution of their software development workflows directly in a project’s repository.
In this tutorial you'll learn how you can automate the workflow of an existing Flask API whose continuous integration process is managed using GitHub Actions. You'll learn about the GitHub actions created by Okteto and how you can use them to work with Okteto with a GitHub Action workflow.
Why automate your workflow?
The Okteto CLI does a fantastic job at simplifying the process of working with your Okteto resources. However, over time, executing commands to build and deploy your docker image to Okteto after major changes could become repetitive and tiring.
According to Google’s SRE practices, a way to eliminate the toil time resulting from such operations is to automate the execution of such commands through the use of a script, or an external automation service.
For this article, we'll be using GitHub Actions as an automation tool to build a docker image from merged code changes and deploy the docker image to Okteto using the available Okteto Actions.
Prerequisites
In order to follow along with this tutorial, it's expected that you satisfy the following requirements;
- Have access to an Okteto instance.
- Have the Okteto CLI installed on your machine.
- Have a GitHub account with Git installed on your machine.
- Have an understanding of the Python programming language, with an installation of python on your machine.
- Have an installation of Docker on your machine.
Step 1: Clone a sample Python Flask app
To get started, fork the sample application from its repository here. After forking the repository, execute the command below from your terminal to clone your forked copy of the repository to your host machine;
Replace the
GITHUB_USERNAMEin the URL below with your GitHub username to match the remote origin of the forked repository.
$ git clone https://github.com/{{GITHUB_USERNAME}}/okteto-flask-app
In the flaskr.py file, there are three API routes defined for performing a create, retrieve, and delete operation against a connected Couch database.
The cloned project also contains a Dockerfile and docker-compose.yml file that contains all defined steps and services needed to create a multi-stage build for this application.
To run this application, execute the docker-compose command below to build and run the application container that consists of a database and app service.
$ docker-compose up --build
To test the application above, execute the command below from a new terminal window to make a POST request to the /api/customer api route within the flask API using cURL which inserts a new document into the customer collection within the running Couch database through it’s RESTful Apiserver.
$ curl -X POST -d '{"name":"Victory Nwani","occupation":"Software Engineer"}' -H 'Content-Type: application/json' http://localhost:5050/api/customer
To view the data inserted from the POST request above, execute the command below from your terminal to make a GET request to the /api/customer api route within the flask API using cURL which retrieves all documents in the customer collection within the couch database.
$ curl http://localhost:5050/api/customer
You can also work with the running couch database through the Fauxton web interface that comes by default with couchdb image at http://localhost:5984/_utils.
As defined in the environment field within the database service in the
docker-compose.ymlfile, the couchdb username is couchdb-admin, while the password is couchdb-password. Feel free to change them to your own preferred secured values.
Step 2: Deploy Flask application To Okteto
Now that you have the cloned application working locally on your computer, you'll deploy the cloned version to Okteto to serve as a deployment for your development environment. The folder already contains a Dockerfile and a docker-compose.yml file that defines the services and steps needed to build the image for this application.
To begin the deployment from your terminal using the Okteto CLI, execute the command below to log in and create a session between your Okteto account and your local terminal;
$ okteto context
Next, build a docker image of the entire application using the docker-compose.yml file and deploy it to your Okteto namespace:
$ okteto deploy --build
Going through the resources listed in your Okteto dashboard, you would find the deployed application, and the two services specified in the docker-compose.yml file.
Step 3: Write unit tests for the Flask application
One important step within any continuous integration pipeline is to Test new commits made to the code source before a new release is pushed to the continuous deployment pipeline to avoid a regression.
To get started, execute the command below from your terminal to create a new git branch where you would create unit tests for the flask API.
$ git checkout -b feat/ci-pipeline
From your code editor, create a tests directory with a test_flask.py file within the okteto-flask-app project. This file would be used to test the three API endpoints within the flask application. Executing the commands below will create the test file, or you can alternatively use the editor interface to create the file.
$ mkdir tests && cd tests
$ touch test_flaskr.py
All HTTP requests that were to be made to the Couch Apiserver from the API routes were intercepted and mocked in the test suites using the Httpretty package.
Add the test suite in the code block below to test the default route handler that returns a response with some information about the REST API.
import os
from requests import get, post
STAGING_API_ENDPOINT = os.environ.get("STAGING_COUCHDB_URL")
def test_handle_default_route():
request = get(STAGING_API_ENDPOINT)
response = request.json()
assert (response['status'] == "OK")
assert 'description' in response
The test suite above asserts that a JSON response containing an “OK” status, and description field will be returned each time a request is made to the default route. Looking through the code block, you might have taken note of the STAGING_API_ENDPOINT variable whose value will be gotten from the STAGING_COUCHDB_URL environment variable. The STAGING_COUCHDB_URL environment variable will be set during the continuous flow, and will point to a staging version of the API within an Okteto namespace. You will learn more about this when setting up the GitHub actions flow.
Next, add the code block below into the test_flaskr.py file to test the route POST request handler within the api/customer endpoint by making a POST HTTP verb to insert a new document into the customer collection.
def test_handle_items_post():
request = post('/api/customer', json={
'name': 'John Mike',
'occupation': 'Software Eng'
})
responseData = request.json()
assert responseData['status'] == "USER CREATED"
The test case above asserts that a status field with a USER CREATED value was returned from the POST request to insert a test document into the customer collection. After the test case above is executed successfully, you can rightly expect that a sample document will exist within the customer collection. The next test case will use this sample document to assert that the GET handler is within the api/customer endpoint.
Add the code block below into the test_flaskr.py file to test the route handling all requests made to the api/customer route with a GET HTTP verb to fetch all documents within the customer collection.
import json
def test_handle_items_fetch():
request = get('{}/api/customer'.format(STAGING_API_ENDPOINT))
data = request.json()
assert (data['status'] == 'OK')
assert 'customers' in data
assert '_id' in data['customers'][0]
When the test case above is executed, a GET request will be made to the GET handler within api/customer route. The test case will further run assertions against the JSON response to ensure that an OK status field was returned, alongside a list of customers.
At this point we have three test suites to test the three corresponding endpoints exposed within this API. In the next step you'll create a GitHub Actions workflow that runs them automatically.
Step 4: Automate deployments with a GitHub Actions workflow
With tests in place, you can create a GitHub Actions workflow that spins up an Okteto preview environment for every pull request and runs your tests against it. Okteto publishes a set of GitHub Actions for this; the two you'll use here are okteto/context, to authenticate against your Okteto instance, and okteto/deploy-preview, to deploy the preview environment.
Configure the required secrets
The workflow authenticates to your Okteto instance using two repository secrets:
OKTETO_TOKEN: an Okteto personal access token. See Personal Access Tokens for how to create one.OKTETO_CONTEXT: the URL of your Okteto instance (for example,https://okteto.example.com).
Add both to your forked repository by following GitHub's encrypted secrets guide. GitHub provides GITHUB_TOKEN automatically.
Add the workflow
Create a .github/workflows/ci.yml file in your repository with the following content:
name: Okteto Flask REST API CI
on:
pull_request:
branches:
- master
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
jobs:
preview:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set context to Okteto
uses: okteto/context@latest
with:
url: ${{ secrets.OKTETO_CONTEXT }}
token: ${{ secrets.OKTETO_TOKEN }}
- name: Deploy preview environment
uses: okteto/deploy-preview@latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
name: pr-${{ github.event.number }}
timeout: 15m
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install python dependencies
run: pip install -r requirements.txt
- name: Run API tests against the preview environment
run: pytest
env:
STAGING_COUCHDB_URL: https://api-pr-${{ github.event.number }}-your-namespace.okteto.example.com
Each time a pull request is opened against your default branch, this workflow authenticates to your Okteto instance, deploys an isolated preview environment for that pull request, and runs the test suite against it. The STAGING_COUCHDB_URL value points pytest at the endpoint Okteto generates for the preview, so your tests exercise a production-like deployment instead of a local mock.
To keep your instance tidy, Okteto can tear the preview down automatically when the pull request is closed, using the okteto/destroy-preview action in a companion workflow. The Preview Environments with GitHub Actions guide walks through the complete, maintained setup, including the cleanup workflow and the exact secret configuration.
Trigger the workflow
Commit and push your changes to a branch in your fork, then open a pull request against the default branch:
$ git add .
$ git commit -m "ci: add tests and Okteto preview workflow"
$ git push -u origin feat/ci-pipeline
When the pull request is opened, GitHub Actions runs the workflow: it deploys the preview environment and runs your tests against it. You can follow the run from the Actions tab of your repository, and Okteto adds the preview environment's endpoints to the pull request so you can review the running application before merging.
Conclusion
In this article you cloned a sample Python application, built and deployed a docker image of the application to Okteto, and automated testing against an Okteto preview environment for every pull request using a GitHub Actions workflow.
For the complete, maintained reference, including the preview cleanup workflow, see Preview Environments with GitHub Actions in the Okteto docs.
The sample application used in this tutorial is available on GitHub in this repository, for you to clone and use.


Arsh Sharma