{"id":16471784,"url":"https://github.com/willis0826/flask-realworld-example-app-ci-cd","last_synced_at":"2026-04-17T13:32:18.648Z","repository":{"id":39835429,"uuid":"233528197","full_name":"Willis0826/flask-realworld-example-app-ci-cd","owner":"Willis0826","description":"The repository demos how to implement a CI/CD pipeline for Flask APP","archived":false,"fork":false,"pushed_at":"2023-02-20T16:10:31.000Z","size":189,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-25T18:03:21.745Z","etag":null,"topics":["flask","gcp","gitlab-ci","kops"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Willis0826.png","metadata":{"files":{"readme":"README-en.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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":"2020-01-13T06:38:01.000Z","updated_at":"2024-07-28T15:52:24.000Z","dependencies_parsed_at":"2025-01-10T23:34:33.238Z","dependency_job_id":"410ea7f9-de11-4918-af42-a1d94214c17a","html_url":"https://github.com/Willis0826/flask-realworld-example-app-ci-cd","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/Willis0826/flask-realworld-example-app-ci-cd","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Willis0826%2Fflask-realworld-example-app-ci-cd","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Willis0826%2Fflask-realworld-example-app-ci-cd/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Willis0826%2Fflask-realworld-example-app-ci-cd/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Willis0826%2Fflask-realworld-example-app-ci-cd/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Willis0826","download_url":"https://codeload.github.com/Willis0826/flask-realworld-example-app-ci-cd/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Willis0826%2Fflask-realworld-example-app-ci-cd/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31931315,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-17T12:37:54.787Z","status":"ssl_error","status_checked_at":"2026-04-17T12:37:25.095Z","response_time":62,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["flask","gcp","gitlab-ci","kops"],"created_at":"2024-10-11T12:14:41.617Z","updated_at":"2026-04-17T13:32:18.632Z","avatar_url":"https://github.com/Willis0826.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"![GitHub Logo](/image.png)\n\n### Flask RealWorld Example App x GitLab CI\n\n[![pipeline status](https://gitlab.com/Willis0826/flask-realworld-example-app-ci-cd/badges/master/pipeline.svg)](https://gitlab.com/Willis0826/flask-realworld-example-app-ci-cd/commits/master)\n\nThe repository is going to introduce how to use GitLab CI to create a pipeline with Test, Pack, Cluster and Deploy stages, and the application used in the repository is [flask-realworld-example-app](https://github.com/gothinkster/flask-realworld-example-app). The Kubernetes cluster in running on [GCE](https://cloud.google.com/compute/), and the Docker image of application is published to [Docker Hub](https://cloud.docker.com/repository/docker/willischou/flask-realworld-example-app/general)\n\nDirectory\n\n  - [GitLab CI and Environment](#gitlab-ci-and-environment)\n  - [Pipeline Stage](#pipeline-stage)\n    - [Test](#test)\n    - [Pack](#pack)\n    - [Cluster](#cluster)\n    - [Deploy](#deploy)\n  - [TODO](#todo)\n\n#### GitLab CI and Environment\n\nBefore we start to run the pipeline, there are 9 environment variables should be set up in the GitLab CI. The instruction of set up environment variables can refer to [Custom Environment Variables](https://docs.gitlab.com/ee/ci/variables/#custom-environment-variables).\n\n`CONDUIT_SECRET` The jwt secret key of flask app  \n`DOCKER_REGISTRY_USER` The Docker Hub user name  \n`DOCKER_REGISTRY_PASSWORD` The Docker Hub password with base64 encoded  \n`GCP_CREDENTIAL_FILE` The GCP service account credential file. The instruction of create a service account and generate credential can refer to [Creating managing service account key](https://cloud.google.com/iam/docs/creating-managing-service-account-keys)  \n`DB_USER` The Postgres DB user  \n`DB_PASSWORD`  The Postgres DB password  \n`K8S_CLUSTER_NAEM` The Kubernetes cluster name which is used by Kops to identify the cluster  \n`KOPS_STATE_STORE` Google storage bucket uri which is used to store the cluster state created by Kops  \n`KOPS_FEATURE_FLAGS` The value should be *AlphaAllowGCE* to enable the Kops create cluster on GCE  \n\n#### Pipeline Stage\n\n##### Test\n\nOn the test stage, there are two jobs unit test and lint. The jobs will check the qulity of code and perform basic unit test.\n\nOn the stage, we use `willischou/python-flask` [Docker Image](https://cloud.docker.com/repository/docker/willischou/python-flask) as GitLab runner image, the image provide python 3.7 runtime. It was built with a simple \b[Dockerfile](https://github.com/Willis0826/docker-base/blob/master/python-flask/Dockerfile).\n\nFor the unit test job(test.app), we install Python package first, and then run the command `flask test` to perform unit test. If there is any error, the pipeline will stop immediately. Because passing the unit test is required.\n\nFor the lint job(lint.app), we install flake8 package first, and then run the command `flask lint` to perform the linter. If there is any error, the pipeline will ignore the error and go on.\n\n##### Pack\n\nOn the pack stage, we are going to build the Docker Image of flask app. For more detail, please check the Dockerfile.\n\nOn the stage, we use `docker:19.03.1` as GitLab runner image and [set up the Docker in Docker for GitLab runner](https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#use-docker-in-docker-workflow-with-docker-executor).\n\nIn the pack job, the sheel script `.ci/docker_pack.sh` will pack and tag the image according to the tag of commit or hash. If the commit has tag, the image tag will use environment variables `$CI_COMMIT_TAG` which is easy to recognize the version in prod. If the commit has no tag, the image tag will use `$CI_COMMIT_SHORT_SHA` which is generally use in dev.\n\nAccording to the Flask app's recommandation, we install [uwsgi](https://flask.palletsprojects.com/en/1.1.x/deploying/wsgi-standalone/#uwsgi) package and running the flask app behind it.\n\nAlso, we pack the database migration script which is under `/migrations` folder alone with Docker image. The migration script will be performed via K8S [Init Containers](https://kubernetes.io/docs/concepts/workloads/pods/init-containers/). After executed the command `flask db upgrade`, the Postgres DB will upgrade with latest database schema.\n\n##### Cluster\n\nOn the cluster stage, we use Kops to deploy our Kubernetes cluster. The manifests for Kops are in `/k8s/kops` folder.\n\nThe stage require two environment varibles `KOPS_STATE_STORE` and `K8S_CLUSTER_NAEM`. `KOPS_STATE_STORE` is a google storage uri which store the cluster states created by Kops. `K8S_CLUSTER_NAEM` is the cluster name used to identify the cluster.\n\nAfter the Kops successfully deployed the cluster, the shell script `.ci/kops_deploy.sh` will start a loop to validate the cluster status utill it is ready.\n\n##### Deploy\n\nOn the deploy stage, we deploy the Docker image built from the pack stage to our Kubernetes cluster\b.\n\nOn the stage, we use `willischou/gcp-gomplate-kubectl` [Docker Image](https://cloud.docker.com/repository/dockerk/willischou/gcp-gomplate-kubectl) as GitLab runner image. The image contains the utils used for K8S deployment such as kubectl, gcloud, gomplate and kops.\n\nIn the shell script `.ci/k8s_deploy.sh`, we use [Gomplate](https://github.com/hairyhenderson/gomplate) to generate the completed manifest which are ready to be applied to the cluster. The manifest template are in the folder `/k8s/app`, `/k8s/postgres`, `k8s/nginx-ingress` and `/k8s/nginx-ingress`. After the manifest were generated, we use the kubectl to apply all the manifests. Finally, a nginx ingress service will be created, and the IP will be shown in the GitLab CI `deploy.nginx-ingress` job.\n\nThe nginx ingress external IP and path for this example is `34.67.218.129/flask/`. You can test the flask app by following the three steps below.\n\n1. Create a user.  \nPOST `34.67.218.129/flask/api/users`  \nHTTP Header  `Content-Type: application/json`  \nHTTP Body  \n\n```json\n{\n    \"user\": {\n        \"username\": \"hello\",\n        \"email\": \"dsa663838@gmail.com\",\n        \"password\": \"abc12345\"\n    }\n}\n```\n\n2. Create a article  \nPOST `34.67.218.129/flask/api/articles`  \nHTTP Header `Authorization: Token \u003cjwt_token\u003e`  \nHTTP Body  \n\n```json\n{\n    \"article\": {\n        \"title\": \"Hi, flask app\",\n        \"description\": \"How to deploy a flask app in minutes\",\n        \"body\": \"First …\"\n    }\n}\n```\n\n3. Get all articles  \nGET `34.67.218.129/flask/api/articles`  \n\n```json\n{\n    \"articles\": [\n        {\n            \"author\": {\n                \"bio\": null,\n                \"email\": \"dsa663838@gmail.com\",\n                \"following\": false,\n                \"image\": null,\n                \"username\": \"hello\"\n            },\n            \"body\": \"First …\",\n            \"createdAt\": \"2019-10-19T10:20:59.198718\",\n            \"description\": \"How to deploy a flask app in minutes\",\n            \"favorited\": false,\n            \"favoritesCount\": 0,\n            \"slug\": \"hi-flask-app\",\n            \"tagList\": [],\n            \"title\": \"Hi, flask app\",\n            \"updatedAt\": \"2019-10-19T10:20:59.198762\"\n        }\n    ],\n    \"articlesCount\": 1\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwillis0826%2Fflask-realworld-example-app-ci-cd","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwillis0826%2Fflask-realworld-example-app-ci-cd","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwillis0826%2Fflask-realworld-example-app-ci-cd/lists"}