{"id":23648043,"url":"https://github.com/cybardev/cycalc","last_synced_at":"2026-05-06T18:37:05.701Z","repository":{"id":266019119,"uuid":"897130976","full_name":"cybardev/CyCalc","owner":"cybardev","description":"Calculator app deployed using DevOps principles, for the ShiftKey DevOps Foundations Certificate program","archived":false,"fork":false,"pushed_at":"2024-12-22T14:45:57.000Z","size":215,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-05-19T22:12:12.156Z","etag":null,"topics":["api","ci-cd","devops","flask","react"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cybardev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-12-02T04:46:17.000Z","updated_at":"2025-02-28T02:37:40.000Z","dependencies_parsed_at":"2025-02-19T05:36:31.078Z","dependency_job_id":null,"html_url":"https://github.com/cybardev/CyCalc","commit_stats":null,"previous_names":["cybardev/cycalc"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/cybardev/CyCalc","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cybardev%2FCyCalc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cybardev%2FCyCalc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cybardev%2FCyCalc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cybardev%2FCyCalc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cybardev","download_url":"https://codeload.github.com/cybardev/CyCalc/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cybardev%2FCyCalc/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266520729,"owners_count":23942323,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-22T02:00:09.085Z","response_time":66,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","ci-cd","devops","flask","react"],"created_at":"2024-12-28T14:51:01.427Z","updated_at":"2026-05-06T18:37:05.649Z","avatar_url":"https://github.com/cybardev.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# CyCalc\n\n## Multi-Container Docker Application with CI/CD: Calculator App Project\n\n\u003e [!NOTE]\n\u003e Complete Project Instructions: [DevOps Foundations Course/Project](https://github.com/shiftkey-labs/DevOps-Foundations-Course/tree/master/Project)\n\nSubmission by - [**Sheikh Saad Abdullah**](https://github.com/cybardev)\n\n---\n\n### Project Overview\n\n**Project description:**\n\nCalculator application with separate containers for frontend and backend, as an experiment to learn more about DevOps principles, containerization, various deployment options, and infrastructure-as-code.\n\n**Files implemented:**\n\n- [this README.md](./README.md)\n  - provides context about the project and the learning experience gained from completing it\n- [backend/Dockerfile](./backend/Dockerfile)\n  - declarative configuration for the backend container image\n- [frontend/Dockerfile](./frontend/Dockerfile)\n  - declarative configuration for the frontend container image\n- [docker-compose.yaml](./docker-compose.yaml)\n  - declarative configuration for running the multi-container calculator application in development\n- [render.yaml](./render.yaml) (Render Blueprint)\n  - declarative configuration that defines the deployment infrastructure on as code\n- [.github/workflows/github-actions.yml](./.github/workflows/github-actions.yml)\n  - declarative configuration that defines the GitHub Actions workflow for running the CI/CD pipeline\n- [.gitignore](./.gitignore)\n  - configuration to ignore specific files and folders from version control (like editor configuration, cache files, etc.)\n- [backend/requirements.txt](./backend/requirements.txt)\n  - file generated through `pip freeze`, to specify required Python package dependencies for the backend\n- [backend/test_app.py](./backend/test_app.py)\n  - Python unit test script for the backend module\n- [backend/gunicorn.config.py](./backend/gunicorn.conf.py)\n  - gunicorn configuration script, allowing to specify API hostname and port via environment variables\n- [frontend/src/App.js](./frontend/src/App.js)\n  - [modified existing file] added trigonometric functions and power function to frontend, and changed `log` to `ln` to avoid confusion\n\n### Docker Implementation\n\n- **Backend Dockerfile** (Python API):\n  - use a base Alpine Linux 3.20 image with Python 3.12 runtime set up\n  - set working directory to an empty directory to isolate application from system files\n  - copy application files from current directory into container image in the working directory set up in previous step\n  - install required dependencies from `requirements.txt` using the `pip` package manager\n  - indicate which port(s) are required to be exposed on the host network\n  - specify default values for non-sensitive environment variables (hostname and port number in this case)\n  - run the Flask application using `gunicorn` server\n\n- **Frontend Dockerfile** (React App):\n  - use a base Alpine Linux 3.20 image with Node JS 23 runtime set up\n  - set working directory to an empty directory to isolate application from system files\n  - copy package dependency specification files into container image\n  - install required dependencies specified in aforementioned files using the `npm` package manager\n  - copy application files from current directory into container image in the working directory set up in second step\n  - build the React application by running the `build` script invoked by the `npm run` command\n  - indicate which port(s) are required to be exposed on the host network\n  - specify default values for non-sensitive environment variables (API hostname, API port number, and if SSL is enabled in the API in this case)\n  - run the React application by running the `start` script invoked by the `npm run` command\n\n- **Building and Distributing container images**:\n  - Local development:\n    - run following commands in the directories with the `Dockerfile`s\n    - build: `docker build -t cycalc-frontend:latest .`\n    - run: `docker run --name cycalc-frontend -p 3000:3000 cycalc-frontend:latest`\n    - for backend, replace `frontend` in above commands with `backend`, and change ports from `3000:3000` to `5000:5000` (or `4000:5000` for macOS, since port 5000 is used by an OS component)\n  - Distributing to container registries:\n    - build and upload container images as GitHub Package to GitHub Container Registry automatically in GitHub Actions workflow\n\n### Docker Compose YAML Configuration\n\n- **Services:**\n  - frontend: the React app, using the `frontend` directory as build context, served over the 3000 port on the local host network. Depends on the backend service. Uses an environment variable to specify which port the backend service is exposed on\n  - backend: the Flask API, using the `backend` directory as build context, served over the 4000 port on the local host network\n- **Networking:**\n  - uses the default bridge network to have isolation between containers but has specified ports exposed so containers can communicate over the local host network\n- **Environment Variables:**\n  - for the frontend, I used an environment variable (`REACT_APP_API_PORT`) to specify what the port number of the Flask backend API would be\n  - I did this because the default 5000 port on my host is occupied by an OS component (on macOS)\n\n### CI/CD Pipeline (YAML Configuration)\n\n- triggers:\n  - on push or merged pull requests on the main branch, but only if Python (`*.py`) or JavaScript (`*.js`) files change\n  - manually run from the GitHub Actions interface (website or hooks, like the VS Code extension)\n- stages:\n  - frontend and backend parallel build and test stages (one for each)\n  - stage to build container image and push to GitHub Container Registry using official workflow\n  - stage to deploy built images on Render via URL web hooks\n\n### Lessons Learned\n\n- port 5000 is occupied by a component of macOS (Control Center)\n- React only reads environment variables prefixed with `REACT_APP_`, to avoid leaking sensitive data from the environment\n- learned how Docker Compose works and how it can be used to set up a smooth development experience for multi-container applications\n- learned multi-container app deployment on Render via their infrastructure-as-code (IaC) model - Render Blueprints. It was interesting to note the similarities between this and Docker Compose\n- learned how to deploy to Render using web hooks from CI/CD pipelines, keeping the hook URL hidden as a repository secret\n- connected custom domain to frontend application in Render: [calc.cybar.dev](https://calc.cybar.dev)\n\n### Future Improvements\n\n- add testing steps in Dockerfile to prevent building bad images\n- optimize Docker images by leveraging multi-stage builds, distroless runtime images, compression software, etc.\n- split CI/CD pipeline into multiple files (test, push to registry, deploy, etc.) for readability, modularity, and separation of concerns\n- add functions and mathematical constants to calculator app frontend: variable base logarithms, _e_, _π_, _i_\n\n---\n\n### Credits\n\n- This repository is a continuation of the work done in the [Project](https://github.com/cybardev/ShiftKey-DevOps/tree/master/Project) directory on this repo: [cybardev/ShiftKey-DevOps](https://github.com/cybardev/ShiftKey-DevOps)\n- The aforementioned repository itself is a fork of the ShiftKey Labs repository: [shiftkey-labs/DevOps-Foundations-Course](https://github.com/shiftkey-labs/DevOps-Foundations-Course)\n- Thanks to [Zainuddin Saiyed](https://github.com/Zain-Saiyed) for teaching the course and guiding us through DevOps, containerization, CI/CD, and relevant concepts.\n- Thanks to [ShiftKey](https://shiftkeylabs.ca/) for hosting the certification program on a topic I'm deeply interested in.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcybardev%2Fcycalc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcybardev%2Fcycalc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcybardev%2Fcycalc/lists"}