Skip to main content

Schedule a Python Script to Run Periodically on a Cloud Server

· 18 min read
Bakar Tavadze

Schedule a Python Script to Run Periodically on a Cloud Server

Cloud servers have a significant advantage over local setups: they're always on. This means that your Python scripts can run uninterrupted 24/7. In this post, we'll explore setting up a Python script to run periodically on 8 different cloud services.

Introduction

When scheduling a Python script to run periodically on a cloud server, there are three main components that we need to consider:

  1. The Python script itself
  2. Requirements (Python packages used in the script)
  3. Environment variables (API keys, etc.)

To demonstrate a complete example, we will use a script that uses the requests package and one environment variable. This way, when we schedule the script on various cloud services, we can see how to handle the requirements and environment variables.

Python Script

import os
import requests


def main():
"""
Gets detailed information about a specified cryptocurrency.
The data includes the coin's ranking, supply details, and current price.
"""

coin_id = os.getenv("COIN_ID")

response = requests.get(f'https://api.coincap.io/v2/assets/{coin_id}')
coin_data = response.json()

print(coin_data)
return coin_data


if __name__ == "__main__":
main()

Requirements

requests

Environment Variables

COIN_ID=bitcoin

Cloud Services

Cron expressions are used to specify schedules on the cloud services presented below. You don't have to know anything about cron expressions to follow along. However, if you want to learn more about them, check out crontab.guru.

We will use * * * * * as the cron expression for most cloud services. This expression will cause the script to run every minute. Some cloud services don't allow running jobs as frequently as every minute. In those cases, we will use a different cron expression.

GCP Cloud Functions

First, we are going to create a Cloud Function that will run our script. Then we will create a Cloud Scheduler job that will trigger the Cloud Function every minute.

Create a Cloud Function

Go to Cloud Functions and click "CREATE FUNCTION".

Creating a Cloud Function on GCP

Give it a name, and select "HTTPS" as the trigger type.

GCP Cloud Function basic configuration setup

Open the "Runtime, build, connections and security settings" dropdown and under the "RUNTIME" tab locate "Runtime environment variables". Add COIN_ID variable with value bitcoin. You can leave everything else as default and click "NEXT".

Adding environment variables to GCP Cloud Function

Set runtime to "Python 3.12" (or whichever Python version you want) and entry point to "main". Paste the following code in main.py:

import functions_framework
import os
import requests

@functions_framework.http
def main(request):
coin_id = os.getenv("COIN_ID")

response = requests.get(f'https://api.coincap.io/v2/assets/{coin_id}')
coin_data = response.json()

print(coin_data)
return coin_data

Adding Python script to GCP Cloud Function

Add requests to requirements.txt so that it looks like this:

functions-framework==3.*
requests

Adding requirements to GCP Cloud Function

We are done with the configuration. Click "DEPLOY".

It will take some time for deployment to complete. Once it is done, we can schedule our Cloud Function to run every minute via Cloud Scheduler. Copy the URL of the Cloud Function. We will need it in the next step.

Copying GCP Cloud Function url

Create a Cloud Scheduler Job

Go to Cloud Scheduler and click "CREATE JOB".

Creating a GCP Cloud Scheduler Job

Give it a name and set frequency to "* * * * *" (every minute). Set timezone to whatever you want. Click "CONTINUE".

Entering a cron expression for GCP Cloud Scheduler Job

Set target type to "HTTP". Enter the URL of the Cloud Function that you copied in the previous step. For "Auth header" select "Add OIDC token". For "Service account" select "Default compute service account". Click "CREATE".

Configuring execution for GCP Cloud Scheduler Job

That's it. The Cloud Function will run every minute. To monitor runs, check the "Logs" tab of your Cloud Function.

Checking GCP Cloud Function logs

AWS Lambda

First, we are going to create a Lambda that will run our script. Then we will create an EventBridge schedule that will trigger Lambda every minute.

Create a Lambda

Go to AWS Lambda and click "Create function".

Creating a Lambda on AWS

Give it a name, set runtime to "Python 3.12" (or whichever Python version you want) and click "Create function".

AWS Lambda basic configuration setup

Under the "Configuration" tab, locate "Environment variables" and add COIN_ID variable with value bitcoin.

Environment variables section for AWS Lambda

Adding environment variables to AWS Lambda

Even though Lambda has an inline code editor where we can write our script, it doesn't allow us to indicate our requirements (in our case the requests package). Therefore, we will have to create a zip file with our script and installed Python packages and upload it to Lambda.

Create a new directory. Open your terminal and cd into it. Execute the following:

pip install requests --target .

This will install the requests package into the root of the directory. Then, create a file called "lambda_function.py" and paste the following code into it:

import os
import requests


def lambda_handler(event, context):
coin_id = os.getenv("COIN_ID")

response = requests.get(f'https://api.coincap.io/v2/assets/{coin_id}')
coin_data = response.json()

print(coin_data)

Next, we need to create a zip file of the contents of the folder. If you are on Linux or Mac, you can do:

zip -r package.zip *

The name of the zip file is not important. However, the structure of the zip file is important. It should have a flat directory structure, with "lambda_function".py and installed package folders installed at the root:

package.zip
|- lambda_function.py
|- requests
|- urllib3
|- certifi
...

AWS Lambda Python zip file structure

Go back to Lambda and upload the zip file.

Uploading a Python zip file on AWS Lambda

Lambda is now ready to run our script. Next, we need to create an EventBridge schedule.

Create an EventBridge Schedule

Go to AWS EventBridge Scheduler and click "Create schedule".

Creating a schedule on AWS EventBridge

Give it a name.

Under "Schedule pattern", select "Recurring schedule" for "Occurrence". Enter "* * * * ? *" (every minute) for "Cron expression". Select "Off" for "Flexible time window" and click "Next".

Indicating a schedule for AWS EventBridge

Under "Target detail", select "AWS Lambda" and under "Invoke" select your Lambda function. Click "Next".

Indicating target for AWS EventBridge schedule

Click "Next" on the next page and then "Create schedule" on the last page.

That's it. Lambda will run every minute. To monitor runs, check the "Monitor" tab of your Lambda and click "View CloudWatch logs".

Monitoring AWS Lambda by viewing AWS CloudWatch logs

AWS EC2

We need to start by launching an EC2 instance. Go to AWS EC2 and click "Launch instance".

Launching an EC2 instance on AWS

Give it a name and select "Ubuntu" as the AMI.

Selecting Ubuntu as AMI on AWS EC2

Scroll down to create a new key pair. We will use this key pair to ssh into the instance. Give it a name and click "Create key pair". The key pair will be downloaded to your computer.

Clicking the key pair link when creating an instance on AWS EC2

Creating a key pair when creating an instance on AWS EC2

Click "Launch instance".

Now we need to ssh into the instance. For that, we need to know the public IP address of the instance. Go to the new instance's page and copy the public IP.

Copying the public ip of an instance on AWS EC2

Open your terminal and cd into the directory where the the key pair was downloaded. If you are on Linux or Mac, make sure that the key pair is not publicly accessible by executing:

chmod 400 <key-pair-name>

Then, execute the following command to ssh into the instance:

ssh -i <key-pair-name> ubuntu@<public-ip>

Replace <key-pair-name> with the the name of your key pair and <public-ip> with public IP of your instance. For example:

ssh -i scheduler.pem [email protected]

SSHing into an AWS EC2 instance

Now we need to create script.py, .env for environment variables, and requirements.txt for requirements. We also need to create a virtual environment and install the requirements in it. Virtual environment allows us to install Python packages specific to our script without affecting the global Python installation.

Let's start by creating the virtual environment. First, we need to install the python3-venv package:

sudo apt-get update
sudo apt-get install python3-venv -y

Then, we need to create the virtual environment and activate it:

python3 -m venv venv
source venv/bin/activate

Next, let's create requirements.txt so that we can install the requirements in the virtual environment. We will use Vim editor to create the file:

vi requirements.txt

This will open the editor. Press i to enter insert mode. Then, paste the following:

requests
python-dotenv

Press esc to exit insert mode. Then, type :wq and press enter to save and exit Vim.

python-dotenv is a package that allows us to read environment variables from a .env file in our Python script.

Let's install the requirements in the virtual environment:

pip install -r requirements.txt

Now we need to create the .env file.

vi .env

Press i to enter insert mode. Then, paste the following:

COIN_ID=bitcoin

Press esc to exit insert mode. Then, type :wq and press enter to save and exit Vim.

Finally, let's create the Python script:

vi script.py

Press i to enter insert mode and paste the following:

import os
import requests
from dotenv import load_dotenv


load_dotenv()

def main():
coin_id = os.getenv("COIN_ID")

response = requests.get(f'https://api.coincap.io/v2/assets/{coin_id}')
coin_data = response.json()
print(coin_data)


if __name__ == "__main__":
main()

Run the script manually to make sure that everything is set up correctly:

python script.py

It should print the data for Bitcoin.

We are going to schedule the script to run every minute. We will use crontab for that. crontab is a config file that describes the commands to run periodically. Let's open the crontab editor:

crontab -e

You will be asked to select an editor. Select 2 for Vim. This will open the editor. Press i to enter insert mode. Use the arrow keys to go to the last line and paste the following:

* * * * * . /home/ubuntu/venv/bin/activate && python3 /home/ubuntu/script.py >> /home/ubuntu/logs.txt

Press esc to exit insert mode. Then, type :wq and press enter to save and exit Vim.

This will run script.py every minute and log the output to logs.txt.

use journalctl -u cron to check each run of the script.

After the first run, logs.txt will be created and you will be able to see the output of the script. To view the logs, execute cat logs.txt.

We've successfully scheduled our script to run every minute. You can safely exit the ssh session by executing exit. The script will continue to run in the background.

DigitalOcean

We will create a Function on DigitalOcean by creating Function files locally and pushing them to DigitalOcean via doctl. We will create the following files: a Python script (__main__.py), a requirements file (requirements.txt), a build script (build.sh), and a DigitalOcean config file (project.yml). Only the config file will be in the root directory. The rest of the files will be in a packages/core/cryptocurrency_checker subdirectory.

The directory structure must look like this:

|- packages/
| |- core/
| | |- cryptocurrency_checker/
| | | |- __main__.py
| | | |- requirements.txt
| | | |- build.sh
|- project.yml

Start by creating the root directory.

Then, create project.yml and paste the following:

packages:
- name: core
functions:
- name: cryptocurrency_checker
runtime: 'python:default'
environment:
COIN_ID: bitcoin

Next, create packages directory. Within packages, create core directory. Within core, create cryptocurrency_checker directory. Inside the cryptocurrency_checker directory, create __main__.py and paste the following:

import json
import os
import requests

def main(args):
coin_id = os.getenv("COIN_ID")

response = requests.get(f'https://api.coincap.io/v2/assets/{coin_id}')
coin_data = response.json()
print(coin_data)

return {
'body': {
'response_type': 'in_channel',
'text': json.dumps(coin_data)
}
}

In the same directory, create requirements.txt and paste the following:

requests

Finally, create build.sh in the same directory and paste the following:

#!/bin/bash

set -e

virtualenv virtualenv
source virtualenv/bin/activate
pip install -r requirements.txt
deactivate

If you are on Linux or Mac, build.sh must be executable. To make it executable, run the following:

chmod +x build.sh

The files are ready. But, we need to install doctl and set it up. Install doctl by following the instructions here: https://docs.digitalocean.com/reference/doctl/how-to/install/.

Then go to Applications & API on DigitalOcean and generate a new token.

Button that generates new token on DigitalOcean

Make sure to check the "Write" box.

Generating a new token on DigitalOcean

To authenticate, copy the token and execute doctl auth init. Paste the token when prompted.

Next, we have to create a serverless namespace. Namespaces are used to group resources together.

doctl serverless namespaces create --label my_namespace --region nyc1

After creating the namespace, we need to connect to it:

doctl serverless connect

At this point, we are ready to deploy our files. Go back to the root directory (where project.yml is located) and execute the following:

doctl serverless deploy . --remote-build

Once the deployment is complete, we can schedule the function to run periodically.

To create a schedule, go to DigitalOcean Functions, open the namespace that we created, and click on your function.

A link to a DigitalOcean Function in the portal

Click on the "Triggers" tab and then "Create Trigger".

A button that creates a DigitalOcean Function trigger

Give it a name, set the cron expression to "* * * * *" (every minute), and save.

Create a Function trigger on DigitalOcean

That's it. The function will run every minute.

Heroku

Start by creating an new app on Heroku.

Creating an app on Heroku

Under the Settings tab, locate and click on "Reveal Config Vars". Add COIN_ID variable with value bitcoin.

Button to view config vars on Heroku

We will be doing deployment via Heroku Git using the Heroku CLI. To install Heroku CLI, follow the instructions here: https://devcenter.heroku.com/articles/heroku-cli#install-the-heroku-cli

Once you have Heroku CLI installed, log in by executing heroku login.

We are ready to create and push files to Heroku Git. We will be pushing a Python script (script.py), a requirements file (requirements.txt) and a Heroku config file (Procfile).

The directory structure must look like this:

|- script.py
|- requirements.txt
|- Procfile

Start by creating the root directory.

Then, create script.py and paste the following:

import os
import requests


def main():
coin_id = os.getenv("COIN_ID")

response = requests.get(f'https://api.coincap.io/v2/assets/{coin_id}')
coin_data = response.json()

print(coin_data)
return coin_data


if __name__ == "__main__":
main()

Next, create requirements.txt and paste the following:

requests

Finally, create Procfile and paste the following:

script: python script.py

To push the files to Heroku, execute the following commands:

git init
heroku git:remote -a function-app
git add .
git commit -am "first commit"
git push heroku master

The app is now deployed. We need to schedule it to run periodically. To do that, we need to install the Cron To Go Scheduler add-on. Execute the following command:

heroku addons:create crontogo:free-trial

To set the schedule, go to your app on Heroku, and open the addon:

Link to open an addon on Heroku

Click on "Add Job".

Link to add a Job via Cron To Go Scheduler on Heroku

Set the schedule to "* * * * *" (every minute). Set the command to python script.py and click "Add job".

Creating a schedule via Cron To Go Scheduler on Heroku

That's it. The script will run every minute. To check the logs, execute the following in the terminal:

heroku logs --tail

Render

We will create a Cron Job on Render by connecting a GitHub repository. This repository will contain a Python script (script.py) and a requirements file (requirements.txt).

The directory structure must look like this:

|- script.py
|- requirements.txt

Start by creating the root directory.

Then, create script.py and paste the following:

import os
import requests


def main():
coin_id = os.getenv("COIN_ID")

response = requests.get(f'https://api.coincap.io/v2/assets/{coin_id}')
coin_data = response.json()

print(coin_data)
return coin_data


if __name__ == "__main__":
main()

Next, create requirements.txt and paste the following:

requests

We are ready to push the files to GitHub. Create a GitHub repository and follow the instructions on GitHub to push the files to it.

Now we need to create a Cron Job on Render by connecting to the GitHub repository that was just created. Go to Create a new Cron Job on Render. Select Build and deploy from a Git repository and click "Next".

Link to connect GitHub on Render to create a Cron Job

Select the GitHub repository that you created and click "Connect".

Connecting to GitHub on Render to create a Cron Job

Give your Cron Job a name, set the schedule to "* * * * *" (every minute), and set the command to python script.py. Scroll down to set an environment variable. Add COIN_ID variable with value bitcoin. Click "Create Cron Job".

Creating a Cron Job on Render

That's it. The Cron Job will run every minute.

Railway

We will create a project on Railway by connecting a GitHub repository. This repository will contain a Python script (script.py), a requirements file (requirements.txt), and a Railway config file (Procfile).

The directory structure must look like this:

|- script.py
|- requirements.txt
|- Procfile

Start by creating the root directory.

Then, create script.py and paste the following:

import os
import requests


def main():
coin_id = os.getenv("COIN_ID")

response = requests.get(f'https://api.coincap.io/v2/assets/{coin_id}')
coin_data = response.json()

print(coin_data)
return coin_data


if __name__ == "__main__":
main()

Next, create requirements.txt and paste the following:

requests

Finally, create Procfile and paste the following:

script: python script.py

We are ready to push the files to GitHub. Create a GitHub repository and follow the instructions on GitHub to push the files to it.

Now we need to create a project on Railway by connecting to the GitHub repository that was just created. Go to railway.app and create a new project.

Link to create a new project on railway.app

Button to deploy from GitHub on Railway

Select the GitHub repository that you created and click "Deploy now".

Now that the project is deployed, we need to add the COIN_ID environment variable. Click on the Variables tab and add COIN_ID variable with value bitcoin.

Adding environment variables to a project on Railway

Next, we have to create a schedule. Click on the Settings tab and scroll down to "Deploy" section. Click on "+ Cron Schedule".

Button to create a schedule for a project on Railway

Railway allows for scheduling scripts to run every 15 minutes at most - not every minute. Therefore, we will use */15 * * * * (every 15 minutes) as the schedule.

That's it. The script will run every 15 minutes.

Vercel

We will create a project on Vercel by connecting a GitHub repository. This repository will contain a Python script (index.py), a requirements file (requirements.txt), and a Vercel config file (vercel.json). The Python script will be in an api directory. The requirements file and the Vercel config file will be in the root directory.

The directory structure must look like this:

|- api/
| |- index.py
|- requirements.txt
|- vercel.json

Start by creating the root directory.

Then, create vercel.json in the root directory and paste the following:

{
"redirects": [{ "source": "/", "destination": "/api" }],
"crons": [{ "path": "/api", "schedule": "0 0 * * *" }]
}

Notice that we are defining a cron job within the crons array. Vercel's Hobby plan allows for scheduling scripts once per day at most - not every minute. Therefore, we are using 0 0 * * * (every day at midnight) as the cron expression.

Next, create requirements.txt in the root directory and paste the following:

requests

Finally, create api directory inside the root directory and create index.py inside the api directory. Paste the following into index.py:

from http.server import BaseHTTPRequestHandler
import os
import requests

class handler(BaseHTTPRequestHandler):

def do_GET(self):
self.send_response(200)
self.send_header('Content-type','text/plain')
self.end_headers()

coin_id = os.getenv("COIN_ID")

response = requests.get(f'https://api.coincap.io/v2/assets/{coin_id}')
coin_data = response.json()

print(coin_data)
return coin_data

We are ready to push the files to GitHub. Create a GitHub repository and follow the instructions on GitHub to push the files to it. Make sure to initialize the repository in the root directory.

Now we need to create a project on Vercel by connecting to the GitHub repository that was just created. Go to vercel.com and create a new project by importing the repository.

Vercel landing page where a project can be created

Importing a GitHub repository to create a project on Vercel

When the repository is imported, you will be able to configure the project. The only thing that we need to do is add the COIN_ID environment variable with value bitcoin and deploy.

Adding an environment variable before deploying a Vercel project

That's it. The script will run every day at midnight.