DEV Community

Joseph D. Marhee
Joseph D. Marhee

Posted on • Originally published at Medium on

Managing Running Container Lifetimes with the Docker Python SDK

I began building a service recently that will, upon user request, create a container (to provide said service, not a Container-as-a-Service-style workload), and then after 1–3 hours, terminate the container (taking the user’s session and local data away with it). The process for this required that I:

  1. Retrieve the container’s StartedAt timestamp
  2. Compare that timestamp to see if it’s been more than however long I’d like to limit a session to.
  3. If a container is over that limit, it is queued for being Stopped and then ultimate the image pruned (in a separate task)

The setup for the script looks something like:

from datetime import datetime
import docker, dateparser 
Enter fullscreen mode Exit fullscreen mode

and then initializing a string for comparison, normalized to look like the Docker API-provided timestamp (these get converted further down, this is just for readability in stdout and, while debugging, I can visually inspect the timestamps):

STARTING_TIME = str(datetime.now()).split(" ")[0]+ "T" + str(datetime.now()).split(" ")[1] + "Z"
Enter fullscreen mode Exit fullscreen mode

then, before we do anything else, setup the Docker client:

client = docker.APIClient(base_url='unix://var/run/docker.sock')
Enter fullscreen mode Exit fullscreen mode

In the SDK, stopping a container requires the use of the Low level API functionality, so unless you require these features, you can leave the client set to docker.from_env() without any further arguments.

def compare_time(run_time,container_started_at):

   dt = dateparser.parse(container_started_at).timestamp()

   mt = dateparser.parse(run_time).timestamp()

   interval = mt - dt

   return interval
Enter fullscreen mode Exit fullscreen mode

This function will take two arguments: the STARTING_TIME above (when the script started running) and the timestamp the container was created, and converts them to seconds, and returns that time in seconds.

def eval_interval(interval):

    eval = (interval / 60 ) / 60

    if eval > 2.0:

        return True

    else:

        return False
Enter fullscreen mode Exit fullscreen mode

then we are checking if that difference in time exceeds, in this case, 2 hours. If it does, it returns True , which in the next function, indicates a running container is safe to terminate:

def check_containers():

    to_delete = []

    for c in client.containers.list():

        start_at = c.attrs['State']['StartedAt']

        interval = compare_time(STARTING_TIME,start_at)

        deleteable = eval_interval(interval)

        print(c.name + " (" + c.id + ") " + str(c.image) + " " + str(deleteable))

        if deleteable == True:

            to_delete.append({"id": c.id, "name": c.name})

        else:

            continue

    return to_delete
Enter fullscreen mode Exit fullscreen mode

The to_delete list this returns is a list of dictionaries that contains the id and name of the containers it detected a deletable value of True , with that in hand, it is handed to a function to delete, or handle:

def delete_containers(to_delete):

    failed_deletions = []

    for c in to_delete:

        try:

            running_container = str(c['id'])

            print("Deleting: " + running_container)

 **print(client.stop(container=running\_container))**

        except:

           failed_deletions.append(c)

           print("Failed to delete: " + c.id)
           continue

    return failed_deletions
Enter fullscreen mode Exit fullscreen mode

This ingests the list of deletable containers, and then creates its own list of new containers that could not be deleted (and then optionally, I can handle this however I want in retry behavior later, but for now, it just throws an exception before continuing).

All of this is executed via a main function:

def main():

    to_delete = check_containers()

    delete_expired = delete_containers(to_delete)

    return delete_expired

if __name__ == " __main__":

    main()
Enter fullscreen mode Exit fullscreen mode

For more about the packages I used:

Top comments (0)