{"id":85646,"date":"2020-06-18T01:42:15","date_gmt":"2020-06-17T23:42:15","guid":{"rendered":"https:\/\/prohoster.info\/blog\/administrirovanie\/ci-cd-v-github-actions-dlya-proekta-na-flask-angular"},"modified":"2020-06-18T01:42:15","modified_gmt":"2020-06-17T23:42:15","slug":"ci-cd-v-github-actions-dlya-proekta-na-flask-angular","status":"publish","type":"post","link":"https:\/\/prohoster.info\/en\/blog\/administrirovanie\/ci-cd-v-github-actions-dlya-proekta-na-flask-angular","title":{"rendered":"CI\/CD in Github Actions for a project on Flask+Angular","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p><img decoding=\"async\" alt=\"CI\/CD in Github Actions for a project on Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/26d0945a41bbf3c98fa92f15be5b8c6c.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nIn this article, I will share my experience of setting up CI\/CD using the Plesk control panel and Github Actions. Today we will learn how to deploy a simple project with the straightforward name \"Helloworld.\" It is written in the Python framework Flask, with workers using Celery and a frontend built with Angular 8.<\/p>\n<p><b>Links to repositories:<\/b> <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\">backend<\/a><\/noindex>, <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld-front\">frontend<\/a><\/noindex>.<\/p>\n<p>In the first part of the article, we will look at our project and its components. In the second part, we will figure out how to set up Plesk and install the necessary extensions and components (DB, RabbitMQ, Redis, Docker, etc.).<\/p>\n<p>In the third part, we will finally figure out how to set up a pipeline to deploy our project on the server in both dev and prod environments. Then we will launch the site on the server.<\/p>\n<p>Oh, and I forgot to introduce myself. My name is Oleg Borzov, and I am a full-stack developer on the CRM team for mortgage loan managers at Domclick. <br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h1>Project Overview<\/h1>\n<p>\nFirst, let's take a look at the two project repositories \u2014 the backend and the frontend \u2014 and go through the code.<\/p>\n<h2>Backend part: Flask+Celery<\/h2>\n<p>\nFor the backend, I chose a combination that is quite popular among Python developers: the Flask framework (for the API) and Celery (for the task queue). SQLAlchemy is used as the ORM. Alembic is used for migrations. Marshmallow is used for JSON validation in the endpoints.<\/p>\n<p>In <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\">the repository<\/a><\/noindex> There is a Readme.md file with detailed descriptions of the structure and instructions for running the project.<\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\/tree\/master\/app_web\">API for the web part<\/a><\/noindex> is quite straightforward, consisting of 6 endpoints:<\/p>\n<ul>\n<li><code>\/ping<\/code> \u2014 for availability checks;<\/li>\n<li>endpoints for registration, authorization, deauthorization, and retrieving the authorized user;<\/li>\n<li>an endpoint for sending emails, which places a task in the Celery queue.<\/li>\n<\/ul>\n<p>\n<noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\/tree\/master\/app_celery\">Celery part<\/a><\/noindex> is even simpler, with just one task <code>send_mail_task<\/code>.<\/p>\n<p>In the folder <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\/tree\/master\/conf\/\">\/conf<\/a><\/noindex> there are two subfolders: <\/p>\n<ul>\n<li><code>docker<\/code> with two Docker files (<code>base.dockerfile<\/code> for building a rarely changing base image and <code>Dockerfile<\/code> for main builds);<\/li>\n<li><code>.env_files<\/code> \u2014 with files containing environment variables for different environments. <\/li>\n<\/ul>\n<p>\nIn the root of the project, there are four docker-compose files:<\/p>\n<ul>\n<li><code>docker-compose.local.db.yml<\/code> for setting up a local database for development;<\/li>\n<li><code>docker-compose.local.workers.yml<\/code> for locally spinning up a worker, DB, Redis, and RabbitMQ;<\/li>\n<li><code>docker-compose.test.yml<\/code> for running tests during deployment;<\/li>\n<li><code>docker-compose.yml<\/code> for deployment.<\/li>\n<\/ul>\n<p>\nAnd the last interesting folder for us \u2014 <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\/tree\/master\/.ci-cd\">.ci-cd<\/a><\/noindex>contains shell scripts for deployment:<\/p>\n<ul>\n<li><code>deploy.sh<\/code> \u2014 starting migrations and deployment. It is run on the server after the build and test runs in Github Actions;<\/li>\n<li><code>rollback.sh<\/code> \u2014 rolls back containers to the previous version of the build;<\/li>\n<li><code>curl_tg.sh<\/code> \u2014 sending deployment notifications to Telegram.<\/li>\n<\/ul>\n<p><\/p>\n<h2>Frontend on Angular<\/h2>\n<p>\n<noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld-front\">Repository with the frontend<\/a><\/noindex> is much simpler than the backend. The frontend consists of three pages:<\/p>\n<ul>\n<li>The main page with a form for sending email and a logout button.<\/li>\n<li>Login page.<\/li>\n<li>Registration page.<\/li>\n<\/ul>\n<p>\nThe main page looks minimalist:<\/p>\n<p><img decoding=\"async\" alt=\"CI\/CD in Github Actions for a project on Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/4db36add2c4277397724b0d71b96ff06.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nThere are two files in the root <code>Dockerfile<\/code> and <code>docker-compose.yml<\/code>, as well as a familiar folder <code>.ci-cd<\/code> with slightly fewer scripts than in the backend repository (scripts for running tests have been removed).<\/p>\n<h1>Create a project in Plesk<\/h1>\n<p>\nLet's start by configuring Plesk and creating a subscription for our website.<\/p>\n<h2>Installing extensions<\/h2>\n<p>\nIn Plesk, we will need four extensions: <\/p>\n<ul>\n<li><code>Docker<\/code> for managing and visually displaying the status of containers in the Plesk admin panel;<\/li>\n<li><code>Git<\/code> to configure the deployment step on the server;<\/li>\n<li><code>Let's Encrypt<\/code> for generating (and auto-renewing) free TLS certificates;<\/li>\n<li><code>Firewall<\/code> for setting up incoming traffic filtering.<\/li>\n<\/ul>\n<p>\nYou can install them through the Plesk admin panel in the Extensions section:<\/p>\n<p><img decoding=\"async\" alt=\"CI\/CD in Github Actions for a project on Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/350cf7d7ca3ae1a965a0d3f8a73594ef.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nWe will not go into detailed configuration of the extensions; the default settings will suffice for our demo purposes.<\/p>\n<h2>Creating a subscription and site<\/h2>\n<p>\nNext, we need to create a subscription for our site helloworld.ru and add the subdomain dev.helloworld.ru.<\/p>\n<ol>\n<li>We create a subscription for the domain helloworld.ru and specify a username-password for the system user:\n<p><img decoding=\"async\" alt=\"CI\/CD in Github Actions for a project on Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/3932dc7492aa523b6cbd540ad7a7fe6b.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nAt the bottom of the page, check the box <i>Secure the domain with Let's Encrypt<\/i>, if we want to configure HTTPS for the site:<\/p>\n<p><img decoding=\"async\" alt=\"CI\/CD in Github Actions for a project on Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/f4d23a34764fdeb411fe2ca9cc95cb45.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Next, in this subscription, we create the subdomain dev.helloworld.ru (for which a free TLS certificate can also be issued):\n<p><img decoding=\"async\" alt=\"CI\/CD in Github Actions for a project on Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/d57e6ba71454cb537917400de4275e92.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<\/ol>\n<p><\/p>\n<h1>Installing server components<\/h1>\n<p>\nWe have a server with <i>OS Debian Stretch 9.12<\/i> and installed control panel <i>Plesk Obsidian 18.0.27<\/i>.<\/p>\n<p>We need to install and configure for our project:<\/p>\n<ul>\n<li>PostgreSQL (in our case, there will be one server with two databases for dev and prod environments).<\/li>\n<li>RabbitMQ (the same, one instance with different vhosts for environments).<\/li>\n<li>Two instances of Redis (for dev and prod environments).<\/li>\n<li>Docker Registry (for local storage of built Docker images).<\/li>\n<li>UI interface for Docker registry.<\/li>\n<\/ul>\n<p><\/p>\n<h2>PostgreSQL<\/h2>\n<p>\nThe PostgreSQL database is already included with Plesk, but it's not the latest version (as of the time of writing, Plesk Obsidian <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.plesk.com\/release-notes\/obsidian\/software-requirements\/\">supported<\/a><\/noindex> Postgres versions 8.4\u201310.8). We want the very latest version for our application (12.3 at the time of writing), so we will install it manually.<\/p>\n<p>There are plenty of detailed instructions for installing Postgres on Debian online (<noindex><a rel=\"nofollow\" href=\"https:\/\/tecadmin.net\/install-postgresql-on-debian-9-stretch\/\">an example<\/a><\/noindex>), so I won\u2019t describe them in detail, just list the commands:<\/p>\n<pre><code class=\"bash\">wget -q https:\/\/www.postgresql.org\/media\/keys\/ACCC4CF8.asc -O - | sudo apt-key add -\nsudo sh -c 'echo \"deb http:\/\/apt.postgresql.org\/pub\/repos\/apt\/ stretch-pgdg main\" &gt;&gt; \/etc\/apt\/sources.list.d\/pgdg.list'\n\nsudo apt-get update\nsudo apt-get install postgresql postgresql-contrib\n<\/code><\/pre>\n<p>\nGiven that PostgreSQL has quite mediocre default settings, it is essential to adjust the configuration. This can be aided by <noindex><a rel=\"nofollow\" href=\"http:\/\/pgconfigurator.cybertec.at\/\">calculator<\/a><\/noindex>: you need to enter your server parameters and replace the settings in the file <code>\/etc\/postgresql\/12\/main\/postgresql.conf<\/code>with the suggested ones. It should be noted that such calculators are not a silver bullet, and the database should be tuned more precisely based on your hardware, application, and query complexity. But this is enough to start.<\/p>\n<p>In addition to the settings suggested by the calculator, we also change the <code>postgresql.conf<\/code>default port 5432 to another one (in our case - <strong>53983<\/strong>).<\/p>\n<p>After changing the configuration file, restart the postgresql-server with the command:<\/p>\n<pre><code class=\"bash\">service postgresql restart\n<\/code><\/pre>\n<p>\nWe have installed and configured PostgreSQL. Now let's create the databases, users for the dev and prod environments, and grant the users permissions to manage the databases:<\/p>\n<pre><code class=\"bash\">$ su - postgres\npostgres:~$ create database hw_dev_db_name;\nCREATE DATABASE\npostgres:~$ create user hw_dev_db_user with password 'hw_dev_db_password';\nCREATE ROLE\npostgres:~$ grant ALL privileges ON database hw_dev_db_name to hw_dev_db_user;\nGRANT\npostgres:~$ create database hw_prod_db_name;\nCREATE DATABASE\npostgres:~$ create user hw_prod_db_user with password 'hw_prod_db_password';\nCREATE ROLE\npostgres:~$ grant ALL privileges ON database hw_prod_db_name to hw_prod_db_user;\nGRANT\n<\/code><\/pre>\n<p><\/p>\n<h2>RabbitMQ<\/h2>\n<p>\nNow let's move on to the installation of RabbitMQ \u2014 the message broker for Celery. It installs quite easily on Debian:<\/p>\n<pre><code class=\"bash\">wget https:\/\/packages.erlang-solutions.com\/erlang-solutions_1.0_all.deb\nsudo dpkg -i erlang-solutions_1.0_all.deb\n\nsudo apt-get update\nsudo apt-get install erlang erlang-nox\n\nsudo add-apt-repository 'deb http:\/\/www.rabbitmq.com\/debian\/ testing main'\nwget -O- https:\/\/www.rabbitmq.com\/rabbitmq-release-signing-key.asc | sudo apt-key add -\n\nsudo apt-get update\nsudo apt-get install rabbitmq-server\n<\/code><\/pre>\n<p>\nAfter installation, we need to create <i>vhosts<\/i>, users, and assign the necessary permissions:<\/p>\n<pre><code class=\"bash\">sudo rabbitmqctl add_user hw_dev_amqp_user hw_dev_amqp_password \nsudo rabbitmqctl set_user_tags hw_dev_amqp_user administrator\nsudo rabbitmqctl add_vhost hw_dev_vhost\nsudo rabbitmqctl set_permissions -p hw_dev_vhost hw_dev_amqp_user \".*\" \".*\" \".*\"\n\nsudo rabbitmqctl add_user hw_prod_amqp_user hw_prod_amqp_password \nsudo rabbitmqctl set_user_tags hw_prod_amqp_user administrator\nsudo rabbitmqctl add_vhost hw_prod_vhost\nsudo rabbitmqctl set_permissions -p hw_prod_vhost hw_prod_amqp_user \".*\" \".*\" \".*\"\n<\/code><\/pre>\n<p><\/p>\n<h2>Redis<\/h2>\n<p>\nNow let's install and configure the last component for our application \u2014 Redis. It will be used as a backend for storing Celery task results.<\/p>\n<p>We will bring up two Docker containers with Redis for the dev and prod environments using the extension <code>Docker<\/code> for Plesk.<\/p>\n<ol>\n<li>We log into Plesk, navigate to the Extensions section, search for the Docker extension, and install it (we need the free version):\n<p><img decoding=\"async\" alt=\"CI\/CD in Github Actions for a project on Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/5638f9b40274e4f7441434279c76a426.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>We go to the installed extension, find the image via search <code>redis bitnami<\/code> and install the latest version:\n<p><img decoding=\"async\" alt=\"CI\/CD in Github Actions for a project on Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/578bbbed6a66a60a4f7410f7d61ea542.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>We enter the downloaded container and adjust the configuration: specify the port, maximum allocated RAM size, password in environment variables, and mount the volume:\n<p><img decoding=\"async\" alt=\"CI\/CD in Github Actions for a project on Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/8bc804b794735412f5e510b3049c5667.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Perform steps 2-3 for the prod container, only changing settings like: port, password, RAM size, and the path to the server volume folder:\n<p><img decoding=\"async\" alt=\"CI\/CD in Github Actions for a project on Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/3af50181c8b3e1ace23a8de11826cb51.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<\/ol>\n<p><\/p>\n<h2>Docker Registry<\/h2>\n<p>\nIn addition to basic services, it would be nice to set up our own Docker image repository on the server. Fortunately, server space is quite cheap now (certainly cheaper than a DockerHub subscription), and the process of installing a private repository is very straightforward. <\/p>\n<p>We want to have the following installed:<\/p>\n<ul>\n<li>a password-protected Docker repository, accessible via a subdomain <noindex><a rel=\"nofollow\" href=\"https:\/\/docker.helloworld.ru\">https:\/\/docker.helloworld.ru<\/a><\/noindex>;<\/li>\n<li>a UI interface for viewing images in the repository, available at <noindex><a rel=\"nofollow\" href=\"https:\/\/docker-ui.helloworld.ru\">https:\/\/docker-ui.helloworld.ru<\/a><\/noindex>.<\/li>\n<\/ul>\n<p>\nFor this:<\/p>\n<ol>\n<li>We will create two subdomains in Plesk within our subscription: docker.helloworld.ru and docker-ui.helloworld.ru, and configure Let's Encrypt certificates for them.<\/li>\n<li>In the folder of the subdomain docker.helloworld.ru, we will add the file <code>docker-compose.yml<\/code> with the following content:\n<pre><code class=\"plaintext\">version: \"3\"\n\nservices:\n  docker-registry:\n    image: \"registry:2\"\n    restart: always\n    ports:\n      - \"53985:5000\"\n    environment:\n      REGISTRY_AUTH: htpasswd\n      REGISTRY_AUTH_HTPASSWD_REALM: basic-realm\n      REGISTRY_AUTH_HTPASSWD_PATH: \/auth\/.htpasswd\n      REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: \/data\n    volumes:\n      - .\/.docker-registry.htpasswd:\/auth\/.htpasswd\n      - .\/data:\/data\n\n  docker-registry-ui:\n    image: konradkleine\/docker-registry-frontend:v2\n    restart: always\n    ports:\n      - \"53986:80\"\n    environment:\n      VIRTUAL_HOST: '*, https:\/\/*'\n      ENV_DOCKER_REGISTRY_HOST: 'docker-registry'\n      ENV_DOCKER_REGISTRY_PORT: 5000\n    links:\n      - 'docker-registry'\n<\/code><\/pre>\n<p>\n<\/li>\n<li>Via SSH, we will generate the .htpasswd file for Basic authentication in the Docker repository:\n<pre><code class=\"plaintext\">htpasswd -bBc .htpasswd hw_docker_admin hw_docker_password<\/code><\/pre>\n<\/li>\n<li>We will build and start the containers:\n<pre><code class=\"plaintext\">docker-compose up -d<\/code><\/pre>\n<\/li>\n<li>And we need to redirect Nginx to our containers. This can be done through Plesk. <\/li>\n<\/ol>\n<p>\nThe following actions need to be performed for the subdomains docker.helloworld.ru and docker-ui.helloworld.ru:<\/p>\n<p>In the section <i>Dev Tools<\/i> of our website, we go to <i>Docker Proxy Rules<\/i>:<\/p>\n<p><img decoding=\"async\" alt=\"CI\/CD in Github Actions for a project on Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/d9c05901d95d61ce5db27a1a7cfddf49.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nAnd we add a rule for proxying incoming traffic to our container:<\/p>\n<p><img decoding=\"async\" alt=\"CI\/CD in Github Actions for a project on Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/faa85cf6da648ef55b7c87c44040efb1.png\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<ol>\n<li>We check that we can authenticate in our container from the local machine:\n<pre><code class=\"plaintext\">$ docker login docker.helloworld.ru -u hw_docker_admin -p hw_docker_password\nWARNING! Using --password via the CLI is insecure. Use --password-stdin.\nLogin Succeeded<\/code><\/pre>\n<\/li>\n<li>We will also check the operation of the subdomain docker-ui.helloworld.ru:\n<p><img decoding=\"async\" alt=\"CI\/CD in Github Actions for a project on Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/fdd6e1396c27c02a88bcbd7efb12f247.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nWhen you click on Browse repositories, the browser will display a login window where you need to enter the login and password for the repository. After that, you will be taken to a page with a list of repositories (it will be empty for you for now):<\/p>\n<p><img decoding=\"async\" alt=\"CI\/CD in Github Actions for a project on Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/f2d75edb530e9d746095ae02d7793c1f.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<\/ol>\n<p><\/p>\n<h2>Opening Ports in Plesk Firewall<\/h2>\n<p>\nAfter installing and configuring the components, we need to open ports to make the components accessible from Docker containers and the external network.<\/p>\n<p>Let's see how to do this using the example of the Firewall extension we installed earlier for Plesk.<\/p>\n<ol>\n<li>Navigate to <i>Tools &amp; Settings &gt; Settings &gt; Firewall<\/i>:<br \/>\n<img decoding=\"async\" alt=\"CI\/CD in Github Actions for a project on Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/d54b4941944fe191574472a475cbaebe.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Navigate to <i>Modify Plesk Firewall Rules &gt; Add Custom Rule<\/i> and open the following TCP ports for the Docker subnet (<strong>172.0.0.0\/8)<\/strong>:<br \/>\nRabbitMQ: 1883, 4369, 5671-5672, 25672, 61613-61614<br \/>\nRedis: 32785, 32786<\/p>\n<p><img decoding=\"async\" alt=\"CI\/CD in Github Actions for a project on Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/3359845f9bd6dc303274da737d4b27a2.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>We will also add a rule to open the PostgreSQL ports and RabbitMQ management panel to the outside world:\n<p><img decoding=\"async\" alt=\"CI\/CD in Github Actions for a project on Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/f812cfc7127da469507538ea48b2c80d.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Apply the rules using the Apply Changes button:\n<p><img decoding=\"async\" alt=\"CI\/CD in Github Actions for a project on Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/e85444ae717aaefa796664003fc86578.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<\/ol>\n<p><\/p>\n<h1>Setting Up CI\/CD in Github Actions<\/h1>\n<p>\nLet's move on to the most interesting part \u2014 configuring the continuous integration and delivery pipeline for our project to the server.<\/p>\n<p>This pipeline will consist of two parts: <\/p>\n<ul>\n<li>building the image and running tests (for the backend) \u2014 on the Github side;<\/li>\n<li>running migrations (for the backend) and deploying containers \u2014 on the server.<\/li>\n<\/ul>\n<p><\/p>\n<h2>Deployment in Plesk<\/h2>\n<p>\nLet's first address the second point (since the first depends on it).<\/p>\n<p>We will configure the deployment process using the Git extension for Plesk.<\/p>\n<p>Let's consider an example with the Prod environment for the Backend repository.<\/p>\n<ol>\n<li>Go to the subscription for our Helloworld site and navigate to the Git section:\n<p><img decoding=\"async\" alt=\"CI\/CD in Github Actions for a project on Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/b55b371560ea87a2954b6553a7cefc49.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>In the 'Remote Git repository' field, paste the link to our Github repository and change the default folder <code>httpdocs<\/code> to another one (for example, <code>\/httpdocs\/hw_back<\/code>):\n<p><img decoding=\"async\" alt=\"CI\/CD in Github Actions for a project on Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/db9ba9adb6e2e1bed5da7ae8e88c76e1.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>Copy the SSH Public key from the previous step and <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/settings\/ssh\/new\">add<\/a><\/noindex> add it in the Github settings.<\/li>\n<li>Click OK on the screen in step 2, after which you will be redirected to the repository page in Plesk. Now we need to set up the repository update on commits to the master branch. For this, go to <i>Repository Settings<\/i> and save the value <code>Webhook URL<\/code> (we will need it later when setting up Github Actions):\n<p><img decoding=\"async\" alt=\"CI\/CD in Github Actions for a project on Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/14d48a357f821d734d40dc434ab3682a.png\" style=\"display:block;margin: 0 auto;\" \/><\/li>\n<li>In the Actions field on the screen from the previous step, enter the script to trigger the deployment:\n<pre><code class=\"plaintext\">cd {REPOSITORY_ABSOLUTE_PATH}\n.ci-cd\/deploy.sh {ENV} {DOCKER_REGISTRY_HOST} {DOCKER_USER} {DOCKER_PASSWORD} {TG_BOT_TOKEN} {TG_CHAT_ID} <\/code><\/pre>\n<p>\nwhere: <\/p>\n<p><code>{REPOSITORY_ABSOLUTE_PATH}<\/code> \u2014 path to the prod backend repository folder on the server;<br \/>\n<code>{ENV}<\/code> \u2014 environment (dev\/prod), in our case <code>prod<\/code>;<br \/>\n<code>{DOCKER_REGISTRY_HOST}<\/code> \u2014 host of our docker repository<br \/>\n<code>{TG_BOT_TOKEN}<\/code> \u2014 Telegram bot token;<br \/>\n<code>{TG_CHAT_ID}<\/code> \u2014 chat\/channel ID for sending notifications.<\/p>\n<p>Example script:<\/p>\n<pre><code class=\"plaintext\">cd \/var\/www\/vhosts\/helloworld.ru\/httpdocs\/hw_back\/\n.ci-cd\/deploy.sh dev docker.helloworld.ru docker_user docker_password 12345678:AAbcdEfghCH1vGbCasdfSAs0K5PALDsaw -1001234567890<\/code><\/pre>\n<\/li>\n<li>Adding a user from our subscription to the Docker group (so they can manage containers):\n<pre><code class=\"plaintext\">sudo usermod -aG docker helloworld_admin<\/code><\/pre>\n<\/li>\n<\/ol>\n<p>\nThe Dev environment for the backend repository and frontend is configured similarly.<\/p>\n<h2>Deployment pipeline in Github Actions<\/h2>\n<p>\nLet's move on to setting up the first part of our CI\/CD pipeline in Github Actions.<\/p>\n<h3>Backend<\/h3>\n<p>\nThe pipeline is described in <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\/blob\/master\/.github\/workflows\/deploy.yml\">the deploy.yml file<\/a><\/noindex>. <\/p>\n<p>But before we dissect it, let's fill in the necessary Secret variables in Github. For this, go to <i>Settings -&gt; Secrets<\/i>:<\/p>\n<ul>\n<li><code>DOCKER_REGISTRY<\/code> \u2014 the host of our Docker repository (docker.helloworld.ru);<\/li>\n<li><code>DOCKER_LOGIN<\/code> \u2014 the login to the Docker repository;<\/li>\n<li><code>DOCKER_PASSWORD<\/code> \u2014 the password for it;<\/li>\n<li><code>DEPLOY_HOST<\/code> \u2014 the host where the Plesk admin panel is available (example: <noindex><a rel=\"nofollow\" href=\"https:\/\/helloworld.ru\">helloworld.ru<\/a><\/noindex>:8443 or <noindex><a rel=\"nofollow\" href=\"http:\/\/123.4.56.78\">123.4.56.78<\/a><\/noindex>:8443);<\/li>\n<li><code>DEPLOY_BACK_PROD_TOKEN<\/code> \u2014 the token for deploying to the prod repository on the server (we obtained it in Deployment in Plesk paragraph 4);<\/li>\n<li><code>DEPLOY_BACK_DEV_TOKEN<\/code> \u2014 the token for deploying to the dev repository on the server.<\/li>\n<\/ul>\n<p>\nThe deployment process is simple and consists of three main steps: <\/p>\n<ul>\n<li>building and publishing the image in our repository;<\/li>\n<li>running tests in a container based on the freshly built image;<\/li>\n<li>deploying to the necessary environment depending on the branch (dev\/master).<\/li>\n<\/ul>\n<p><\/p>\n<h3>Frontend<\/h3>\n<p>\n<noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld-front\/blob\/master\/.github\/workflows\/deploy.yml\">The deploy.yml file for the frontend repository<\/a><\/noindex> doesn't differ much from the backend one. It lacks the step for running tests and the names of the tokens for deployment are changed. By the way, secrets for the frontend repository need to be filled in separately.<\/p>\n<h1>Configuring the site<\/h1>\n<p><\/p>\n<h2>Proxying traffic through Nginx<\/h2>\n<p>\nWell, we have reached the end. Now we just need to set up proxying of incoming and outgoing traffic to our container through Nginx. We have already covered this process in section 5 of the Docker Registry setup. The same needs to be repeated for the backend and frontend in both dev and prod environments.<\/p>\n<p>I will provide screenshots of the settings.<\/p>\n<h3>Backend<\/h3>\n<p>\n<img decoding=\"async\" alt=\"CI\/CD in Github Actions for a project on Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/f3c38dc1c25b648db000c72ef2d30b2a.png\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<h3>Frontend<\/h3>\n<p>\n<img decoding=\"async\" alt=\"CI\/CD in Github Actions for a project on Flask+Angular\" src=\"\/wp-content\/uploads\/2020\/06\/35d4d8ef3489608db1e173b7f214ca03.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<strong>Important clarification<\/strong>. In the frontend container, all URLs will be proxied except those starting with <code>\/api\/<\/code> \u2014 they will be proxied to the backend container (therefore, <strong>in the backend container, all handlers must start with <\/strong><strong><code>\/api\/<\/code><\/strong>).<\/p>\n<h2>Summary<\/h2>\n<p>\nNow our site should be accessible at helloworld.ru and dev.helloworld.ru (prod and dev environments respectively).<\/p>\n<p>In summary, we learned how to prepare a simple application using Flask and Angular and set up a pipeline in Github Actions for deploying it to a server managed by Plesk.<\/p>\n<p>I will duplicate the links to the repositories with the code: <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld_back\">backend<\/a><\/noindex>, <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/olegborzov\/helloworld-front\">frontend<\/a><\/noindex>.<br \/>\n<br \/>Source: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/domclick\/blog\/506802\/\">habr.com<\/a> <\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u043f\u043e\u0434\u0435\u043b\u044e\u0441\u044c \u0441\u0432\u043e\u0438\u043c \u043e\u043f\u044b\u0442\u043e\u043c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 CI\/CD \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u043f\u0430\u043d\u0435\u043b\u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f Plesk \u0438 Github Actions. \u0421\u0435\u0433\u043e\u0434\u043d\u044f \u0431\u0443\u0434\u0435\u043c \u0443\u0447\u0438\u0442\u044c\u0441\u044f \u0434\u0435\u043f\u043b\u043e\u0438\u0442\u044c \u043f\u0440\u043e\u0441\u0442\u0435\u043d\u044c\u043a\u0438\u0439 \u043f\u0440\u043e\u0435\u043a\u0442 \u0441 \u043d\u0435\u0437\u0430\u043c\u044b\u0441\u043b\u043e\u0432\u0430\u0442\u044b\u043c \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435\u043c \u00abHelloworld\u00bb. \u041e\u043d \u043d\u0430\u043f\u0438\u0441\u0430\u043d \u043d\u0430 Python-\u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0435 Flask, \u0441 \u0432\u043e\u0440\u043a\u0435\u0440\u0430\u043c\u0438 \u043d\u0430 Celery \u0438 \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u043e\u043c \u043d\u0430 Angular 8. \u0421\u0441\u044b\u043b\u043a\u0438 \u043d\u0430 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438: \u0431\u044d\u043a\u0435\u043d\u0434, \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434. \u0412 \u043f\u0435\u0440\u0432\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u0441\u0442\u0430\u0442\u044c\u0438 \u043c\u044b \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043d\u0430 \u043d\u0430\u0448 \u043f\u0440\u043e\u0435\u043a\u0442 [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":85647,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-85646","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.2 - aioseo.com -->\n\t<meta name=\"description\" content=\"\u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u043f\u043e\u0434\u0435\u043b\u044e\u0441\u044c \u0441\u0432\u043e\u0438\u043c \u043e\u043f\u044b\u0442\u043e\u043c.\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Yuri Gagarin\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/prohoster.info\/en\/blog\/administrirovanie\/ci-cd-v-github-actions-dlya-proekta-na-flask-angular\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.2\" \/>\n\t\t<meta property=\"og:locale\" content=\"en_US\" \/>\n\t\t<meta property=\"og:site_name\" content=\"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"\ud83e\udd47CI\/CD \u0432 Github Actions \u0434\u043b\u044f \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u043d\u0430 Flask+Angular | ProHoster\" \/>\n\t\t<meta property=\"og:description\" content=\"\u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u043f\u043e\u0434\u0435\u043b\u044e\u0441\u044c \u0441\u0432\u043e\u0438\u043c \u043e\u043f\u044b\u0442\u043e\u043c.\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/en\/blog\/administrirovanie\/ci-cd-v-github-actions-dlya-proekta-na-flask-angular\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:width\" content=\"350\" \/>\n\t\t<meta property=\"og:image:height\" content=\"350\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2020-06-17T23:42:15+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2020-06-17T23:42:15+00:00\" \/>\n\t\t<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"\ud83e\udd47CI\/CD in Github Actions for a project using Flask+Angular | ProHoster","description":"In this article, I will share my experience.","canonical_url":"https:\/\/prohoster.info\/en\/blog\/administrirovanie\/ci-cd-v-github-actions-dlya-proekta-na-flask-angular","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"en_US","og:site_name":"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b","og:type":"article","og:title":"\ud83e\udd47CI\/CD \u0432 Github Actions \u0434\u043b\u044f \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u043d\u0430 Flask+Angular | ProHoster","og:description":"\u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u043f\u043e\u0434\u0435\u043b\u044e\u0441\u044c \u0441\u0432\u043e\u0438\u043c \u043e\u043f\u044b\u0442\u043e\u043c.","og:url":"https:\/\/prohoster.info\/en\/blog\/administrirovanie\/ci-cd-v-github-actions-dlya-proekta-na-flask-angular","og:image":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:secure_url":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:width":350,"og:image:height":350,"article:published_time":"2020-06-17T23:42:15+00:00","article:modified_time":"2020-06-17T23:42:15+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"85646","title":null,"description":null,"keywords":null,"keyphrases":null,"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"seo_analyzer_scan_date":null,"breadcrumb_settings":null,"limit_modified_date":false,"reviewed_by":null,"ai":null,"created":"2021-02-28 13:12:50","updated":"2022-09-29 06:56:26","focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"gt_translate_keys":[{"key":"link","format":"url"}],"_links":{"self":[{"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/posts\/85646","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/comments?post=85646"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/posts\/85646\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/media\/85647"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/media?parent=85646"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/categories?post=85646"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/tags?post=85646"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}