A beginner ATM simulator written in October 2020 for a high-school computer
science hackathon, and later retrofitted with a small web front end and a
container pipeline. Two programs sit side by side in this repository: the
original menu-driven terminal program (ATM-Python.py), kept as it was
written, and a Flask rewrite of the same account model (app.py) that is built
into a Docker image by GitHub Actions and deployed to Kubernetes through
Argo CD. It is meant for people learning Python, and for anyone who wants a
tiny self-contained app to practise a CI/CD pipeline on.
Nothing here talks to a real bank. Balances and bills are hardcoded starting values that live in memory (CLI) or in the browser session (web app).
Context: High-school computer science hackathon, October 2020, Best Beginner Hack.
Terminal program - ATM-Python.py
- PIN login with three attempts before the account locks
- View balance
- Deposit cash
- Withdraw cash, refused when the balance would not cover it
- Pay electric, gas and water bills from a repeating bill menu
Web app - app.py
- Single-page ATM served by Flask, with the balance and the three bills held in a signed session cookie
- Deposit and withdraw forms, with insufficient-balance handling
- Bill payment for the same three utilities, with the selection validated server-side
- No login screen: the web app is the transaction menu only
Delivery
Dockerfileproducing apython:3.14-slimimage that listens on port 8080- Kubernetes
Deployment+Servicemanifests and an Argo CDApplication - GitHub Actions pipeline that builds on every pull request and publishes to
Docker Hub from
main
Python 3.14 · Flask 3.1.3 · Docker · Kubernetes · Argo CD · GitHub Actions
- Python 3.11 or newer for a local run. The container image and CI build on Python 3.14, which is the version the app is verified against. Run the project with Python 3, not Python 2.7 - the code uses Python 3 syntax and will not run on 2.7.
- Docker, only if you want to build or run the container image.
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python3 app.pyThen open http://localhost:8080.
docker build -t atm-python-app .
docker run --rm -p 8080:8080 -e FLASK_SECRET_KEY="$(python3 -c 'import secrets; print(secrets.token_hex(32))')" atm-python-app| Name | Required | Purpose |
|---|---|---|
FLASK_SECRET_KEY |
No locally, yes in deployment | Signs the session cookie. When it is unset a random key is generated per process, so sessions reset on restart and do not work across replicas. |
FLASK_DEBUG |
No | Set to 1 to enable the Werkzeug debugger. Off by default, and it must stay off anywhere the port is reachable, because the debugger is a remote shell. |
Run the original terminal program:
python3 ATM-Python.pyThe PIN is 1234. This login exists in the terminal program only.
The web app at http://localhost:8080 is the transaction menu only: deposit, withdraw, and pay the electric, gas or water bill.
.
├── ATM-Python.py # 2020 hackathon program, terminal menu
├── app.py # Flask rewrite of the same account model
├── requirements.txt # Python dependencies for app.py (pinned)
├── Dockerfile # python:3.14-slim image, serves on :8080
├── k8s-deployment.yaml # Kubernetes Deployment + ClusterIP Service
├── argocd-app.yaml # Argo CD Application, automated sync
├── docs/assets/ # README images
└── .github/
└── workflows/
└── docker-image.yml # build on PR, build and push on main
kubectl apply -f k8s-deployment.yamlargocd-app.yaml registers the repository with Argo CD and syncs the manifests
automatically, with prune and selfHeal enabled.
Two things to know before pointing this at a real cluster: the Deployment
tracks the mutable :latest tag with imagePullPolicy: IfNotPresent, so a node
that already holds an older :latest will not pick up a new build - use the
commit-SHA tag for anything you want to roll deterministically. And the
Deployment sets no resource requests, limits, or health probes.
The pipeline lives in .github/workflows/docker-image.yml.
- Triggers. Pushes to
main, pull requests targetingmain, and manualworkflow_dispatchruns. The push and pull request triggers are filtered to the paths that can change the image:app.py,ATM-Python.py,Dockerfile,requirements.txtand the workflow file itself. - Pull requests build only. The Docker Hub login step is skipped and
pushisfalse, so a pull request - including one from a fork - never reaches the registry credentials. A brokenDockerfilestill fails the check. mainbuilds and pushes. The image is published ascanduru04/atm-python-app:latestandcanduru04/atm-python-app:<commit sha>, so every build is also addressable by the commit that produced it.- Actions are pinned to commit SHAs, with the release tag in a trailing comment. A retagged upstream release cannot silently change what runs in CI. Bump the SHA and the comment together by hand when a new release appears.
- Least privilege. The workflow declares
permissions: contents: readat the top level, so the automaticGITHUB_TOKENcannot write to the repository.
Repository secrets used by the pipeline, by name: DOCKERHUB_USERNAME and
DOCKERHUB_TOKEN. They are only read on main and manual runs.
MIT. See LICENSE. Copyright (c) 2020-2026 Can Duru.
Can Duru — canduru.net