Thinkube GitOps

Change your app and ship it

Edit the code, run its tests, push, and the new version is live

Level
beginner
Time
30 min
Risk
low
Updated
2026-10-04

Web appGitTests

Overview

Basic idea

Shipping a change is one push. In a GitOps platform the repository is the only thing you touch: you commit and push, and the platform tests the code, builds new images and replaces the running version. What runs is always a commit you can read, and going back is pushing the previous one.

  • Your checkout is the working copy. The app is already checked out in Thinkube IDE, at ~/apps/<name>.

  • Tests guard the push. The build runs the app’s tests first; a failing test stops the release.

  • The push is the deploy.

What you’ll accomplish

You add an endpoint to the notes app that counts your tasks, run its test in Thinkube IDE, push it, and call it on the live app.

What to know before starting

Required

Optional

Supported hardware

  • GPU: not needed.

  • Architecture: amd64 or arm64: the app’s images are built for both, so it runs on any node.

Prerequisites

Platform

  • Thinkube IDE open, with ~/apps/notes in the workspace.

Components

  • None.

Instructions

Step 1. Make the change

Ask your agent:

› in apps/notes add GET /api/v1/tasks/count that returns how many tasks I have and how many are done, with a test

On the reference run the agent added a route to backend/app/api/tasks.py, before the route that takes a task id so count is not read as an id:

@router.get("/count", response_model=TaskCount)
async def count_tasks(
    db: Session = Depends(get_db),
    current_user: dict = Depends(get_current_user)
):
    """
    Count the current user's tasks.
    Requires authentication.
    """
    mine = db.query(TaskModel).filter(TaskModel.user_id == current_user.sub)
    return TaskCount(
        total=mine.count(),
        done=mine.filter(TaskModel.status == TaskStatus.DONE).count(),
    )

and a test file, backend/tests/test_task_count.py, that creates three tasks, two of them done, and expects {"total": 3, "done": 2}.

Step 2. Run the test

Every container of the template has a run_tests.sh: with no argument it runs the whole suite, with a file it runs that file only. The tests use the platform’s PostgreSQL, and every test’s rows are rolled back when it ends.

Ask your agent:

› run backend/tests/test_task_count.py in apps/notes

Or by hand:

cd ~/apps/notes/backend && ./run_tests.sh tests/test_task_count.py

Expected output:

tests/test_task_count.py::test_task_count_unauthenticated PASSED
tests/test_task_count.py::test_task_count_counts_done PASSED
======================== 2 passed, 11 warnings in 0.17s ========================

Step 3. Commit and push

Ask your agent:

› commit the task count and push notes

Or by hand:

git add backend/app/api/tasks.py backend/tests/test_task_count.py
git commit -m "Count tasks: GET /api/v1/tasks/count"
git pull --rebase
git push

The push starts the build: the backend and frontend tests, the images for amd64 and arm64, then the rollout.

Step 4. Call it on the live app

Ask your agent:

› tell me when /api/v1/tasks/count is live on notes, then call it as me

Expected output: on the reference run the endpoint appeared in the app’s API description, https://notes.thinkube.com/api/v1/openapi.json, 171 seconds after the push. Called with the signed-in user’s token:

{"total":1,"done":0}

Without a token it answers {"detail":"Not authenticated"}, as every endpoint of the app does.

Step 5. Next steps

To undo: Revert the commit and push.

Troubleshooting

Symptom Cause Fix

A test run in Thinkube IDE fails with fe_sendauth: no password supplied

run_tests.sh reads the database login from ADMIN_USERNAME and ADMIN_PASSWORD, and the IDE terminal does not set them

Load them before the run: set -a; . ~/.env; set +a; export ADMIN_USERNAME=tkadmin, then run the test again.

git push is rejected with ! [rejected] main → main (non-fast-forward)

After every deploy the build commits the new image tag to the repository, so your checkout is one commit behind

Run git pull --rebase, then git push again.