<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sergii Lischuk</title>
    <description>The latest articles on DEV Community by Sergii Lischuk (@leefrost).</description>
    <link>https://dev.to/leefrost</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F744749%2Fcdb59a7b-efb2-4272-b358-58a61eddfb04.jpeg</url>
      <title>DEV Community: Sergii Lischuk</title>
      <link>https://dev.to/leefrost</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/leefrost"/>
    <language>en</language>
    <item>
      <title>Apache Airflow. How to make the complex workflow as an easy job</title>
      <dc:creator>Sergii Lischuk</dc:creator>
      <pubDate>Sun, 20 Feb 2022 18:22:51 +0000</pubDate>
      <link>https://dev.to/leefrost/apache-airflow-how-to-make-the-complex-workflow-as-an-easy-job-4a0p</link>
      <guid>https://dev.to/leefrost/apache-airflow-how-to-make-the-complex-workflow-as-an-easy-job-4a0p</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;A couple of weeks ago I started to work with this platform in terms of a feature request. The feature was connected with GCP, observation, and tons of data to be processed. I was looking for something really powerful to make the flow easy and clean to create and run the jobs. And we should not forget consistency, fault tolerance, and correct error handling as well.&lt;/p&gt;

&lt;p&gt;My research brings me to the orchestration topic, especially to the Apache Airflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why do you need an orchestration?
&lt;/h2&gt;

&lt;p&gt;If you have just a simple task to export some data to Excel, maybe, you don't need to use orchestration at all. But, if you are working with data, which brings you a really good profit after the processing, or, you mainly working with a large amount of non-clean data daily - seems to be you are in the right place to start thinking about it.&lt;/p&gt;

&lt;p&gt;For example, if your company processes a big amount of data and gives your customers good advice and profits from it, almost in all cases the workflow will be the same. Each night somewhere in S3-bucket/Azure Blob storage your providers will create some files with raw data inside. Secondly, you collect that data and aggregate it structurally (eg, push to BigQuery table). Further, you process it with complicated SQL scripts, make (again) aggregation, and patch some invalid data. After, you need to check that data with some external API (could be even your special services), but the data size is too big to start working without parallelism or queue processing. And finally, after the validation, you need to connect the final result with some data-preview tools (like Tableau dashboard) to show it to your customers.&lt;/p&gt;

&lt;p&gt;As we can see, this process is not so easy at the first glance. And there are much more cases (pipelines) to be handled in real life. &lt;/p&gt;

&lt;p&gt;For this reason, you need to have a workflow manager. And for the last couple of years, de-facto, this is Apace Airflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  History
&lt;/h2&gt;

&lt;p&gt;Airflow was born like an internal project in Airbnb, in 2014. From the start, it was an open-source project, so, it was easy to provide appropriate functionality with PR as fast as it is possible. In 2016, the project moves to Apache Incubator and in 2019 Airflow becomes a top-level project in Apache Software Foundation. &lt;/p&gt;

&lt;h2&gt;
  
  
  Components
&lt;/h2&gt;

&lt;p&gt;Airflow is a python project, so, almost all the functionality is a python code. &lt;/p&gt;

&lt;p&gt;To start working with Airflow, you need to provide a configuration. And the configuration strongly depends on the number of parallel tasks which will do the job. &lt;/p&gt;

&lt;p&gt;Also, there are required components, which will be always detected on your checklist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Metadata database - database, where Airflow saves all meta-information regarding the current and past tasks, statutes, and results. I will recommend using Postgres here (more stable and effective workflow), but there are configuration and connections for MYSQL, MSSQL, and SQLite as well.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Scheduler - system component, which parses the files with pipeline descriptions and pushes them to the &lt;code&gt;Executor&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Web Server - Flask-based app, running through gunicorn. The main goal is to show visually the pipeline process and provide control over it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Executor - special part, which runs the code (job)(see Execution)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also, there are components, which are task-depended:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Triggerer - in a simple way - is an event-loop for async operators. Currently, there are not many of them, so, you need to think before regarding the &lt;code&gt;Triggerer&lt;/code&gt; component in your workflow&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Worker - modified worker from Celery lib. The small node where Celery can run your task.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Execution
&lt;/h2&gt;

&lt;p&gt;Python code that describes the job must be executed somewhere. This part belongs to &lt;code&gt;Executor&lt;/code&gt;. Airflow supports the next kind of executors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;SequentialExecutor - run code locally, in the main thread of the Airflow;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;LocalExecutor - run code locally, but in different processes of OS&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;CeleryExecutor - do the job in Celery worker (Celery lib)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;DaskExecutor - in Dask cluster&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;KubernetesExecutor - in k8 pods&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From my experience, production code is based on Celery/Kubernetes executors. You need to take in mind this fact, cuz you must be careful with dependencies between the tasks in the pipeline. Every task will be running in its isolated environment, and, with high possibility on different physical devices (computers). So, the sequence of tasks "download file to disk" and "upload file to cloud storage" will not work correctly. More detailed information you can find &lt;a href="https://www.astronomer.io/guides/airflow-executors-explained" rel="noopener noreferrer"&gt;here&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;As you may see, Airflow is very customizable. Configuration can be made in most custom ways to be as close as possible with requirements.&lt;/p&gt;

&lt;p&gt;In general, there are 2 most spread architectures: &lt;a href="http://site.clairvoyantsoft.com/setting-apache-airflow-cluster/" rel="noopener noreferrer"&gt;single-node and multi-node&lt;/a&gt;: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0vjinhpbln377kmc3ep2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0vjinhpbln377kmc3ep2.png" alt="Single node"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frdmiiolloqrkq7rpr4o5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frdmiiolloqrkq7rpr4o5.png" alt="Multi node"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;There are several ways to install Apache Airflow. Let's check them.&lt;/p&gt;

&lt;h3&gt;
  
  
  - PIP package manager
&lt;/h3&gt;

&lt;p&gt;Not an easy way. First of all, you need to install all dependencies, after - installing and configuring DB (with SQLite you are restricted to use only &lt;code&gt;SequentialExecutor&lt;/code&gt;). A good practice is to initialize python virtual env, and then start working with Airflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python -m pip install apache-airflow
airflow webserver
airflow scheduler
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  - Separated Docker images
&lt;/h3&gt;

&lt;p&gt;I found this useful when you are trying to run Airflow on bare-metal servers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run … postgres
docker run … apache/airflow scheduler
docker run … apache/airflow webserver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  - Docker compose
&lt;/h3&gt;

&lt;p&gt;In my opinion - the clean and easy way. You just need to create a docker-compose file with all configurations inside, so you will be able to reuse different variables and connections:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  - Astronomer CLI
&lt;/h3&gt;

&lt;p&gt;I did not work so much with this tool, but it has a good community around. Also, they have an internal registry for any hooks/operators which will simplify the working process with Airflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Base concept
&lt;/h2&gt;

&lt;p&gt;The main entity in this story is DAG (direct acyclic graph) - the housekeeper of tasks. Its spread title, you can meet it in different languages.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6qx12684pvu4o3iv42dn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6qx12684pvu4o3iv42dn.png" alt="Tasks"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The edges of this graph are &lt;code&gt;Task&lt;/code&gt;, which is an instance of &lt;code&gt;Operator&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;All operators, in general, can be divided into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Action operator -  make some action (ReloadJobOperator, etc.)&lt;/li&gt;
&lt;li&gt;Transfer operator - migrate data from one place to another (S3ToGCPOperator)&lt;/li&gt;
&lt;li&gt;Sensor operator - wait some action (BQTablePartitionSensor)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each pipeline is working inside &lt;code&gt;Task Instance&lt;/code&gt; - an instance of the operator with timespan (when this operator is started). You are also able to configure &lt;code&gt;Variables&lt;/code&gt; and &lt;code&gt;Connections&lt;/code&gt; - environment variables, which are responsible for holding different connection strings, logins, etc. With the Web part, you can configure them in the UI.&lt;/p&gt;

&lt;p&gt;Last but not least - &lt;code&gt;Hook&lt;/code&gt; - is an interface for external services. Hooks are wrappers around popular libraries, APIs, DBs. E.g. - if you need to handle a connection to some SQL server, you can start thinking regarding SqlServiceHook (and it is already exist).&lt;/p&gt;

&lt;h3&gt;
  
  
  Create DAGs. Main moments
&lt;/h3&gt;

&lt;p&gt;First of all, you need some declarations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;airflow.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DAG&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;airflow.operators.python&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PythonOperator&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, let's create 2 functions for download data and pivot it (do not forget to check the executor, to be sure if these 2 tasks will be running in one place)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;download_data_fn&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
   &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://web.stanford.edu/class/archive/cs/cs109/cs109.1166/stuff/titanic.csv&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
   &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;titanic.csv&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;pivot_data_fn&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
   &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;titanic.csv&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pivot_table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Sex&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Pclass&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;aggfunc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;count&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reset_index&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;to_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;titanic_pivoted.csv&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And, the final step is to create DAG with execution order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;DAG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dag_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;titanic_dag&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;schedule_interval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*/9 * * * *&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;dag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="n"&gt;download_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PythonOperator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
       &lt;span class="n"&gt;task_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;download_data&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;python_callable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;download_data_fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;dag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="p"&gt;)&lt;/span&gt;

   &lt;span class="n"&gt;pivot_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PythonOperator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
       &lt;span class="n"&gt;task_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pivot_data&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;python_callable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pivot_data_fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;dag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="p"&gt;)&lt;/span&gt;

   &lt;span class="n"&gt;download_data&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pivot_data&lt;/span&gt;

&lt;span class="c1"&gt;# variants:
# pivot_data &amp;lt;&amp;lt; download_data 
# download_data.set_downstream(pivot_data)
# pivot_data.set_upstream(download_data)
&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Created file must be located in folder, where all DAGs are located. By default, it is - $AIRFLOW_HOME/dags. If it is so - scheduler will take it to the execution order, and an Executor will run it every 9 minutes.&lt;/p&gt;

&lt;h3&gt;
  
  
  XComs
&lt;/h3&gt;

&lt;p&gt;Sometimes we have dependencies between Task A and Task B. We do want not just to run tasks one after another, but also pass some results like pipes in the console. For this purpose, we can use XComs.&lt;/p&gt;

&lt;p&gt;With XComs (cross-task communication) one task can write special metadata to metadata DB and another can read that data. We can take the previous example and modify it a little bit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;download_data_fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
   &lt;span class="n"&gt;filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;titanic.csv&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
   &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://web.stanford.edu/class/archive/cs/cs109/cs109.1166/stuff/titanic.csv&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
   &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
   &lt;span class="c1"&gt;#context['ti'].xcom_push(key='filename', value=filename) # option 1
&lt;/span&gt;   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt; &lt;span class="c1"&gt;# option 2
&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;pivot_data_fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ti&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
   &lt;span class="c1"&gt;# filename = ti.xcom_pull(task_ids=['download_data'], key='filename') # option 1
&lt;/span&gt;   &lt;span class="n"&gt;filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ti&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;xcom_pull&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task_ids&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;download_data&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;return_value&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# option 2
&lt;/span&gt;   &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pivot_table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Sex&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Pclass&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;aggfunc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;count&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reset_index&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;to_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;titanic_pivoted.csv&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;DAG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dag_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;titanic_dag&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;schedule_interval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*/9 * * * *&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;dag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="n"&gt;download_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PythonOperator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
       &lt;span class="n"&gt;task_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;download_data&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;python_callable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;download_data_fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;provide_context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="p"&gt;)&lt;/span&gt;

   &lt;span class="n"&gt;pivot_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PythonOperator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
       &lt;span class="n"&gt;task_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pivot_data&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;python_callable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pivot_data_fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;provide_context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="p"&gt;)&lt;/span&gt;

   &lt;span class="n"&gt;download_data&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pivot_data&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As we can see, there are different ways to use XCom objects, but you should keep in mind that data must be small. If the data size will big, you will spend time-saving that data to DB and can reach the limits of meta DB. Secondly, Airflow is just an orchestrator and must not be used for data processing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Downsides
&lt;/h2&gt;

&lt;p&gt;You need to know and keep in mind a lot of things to get good results. It's a complicated tool, but its do complex work. Also, in most cases for debugging and tracing you will have a local instance of Airflow, so you will have some stagging and prod environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alternatives
&lt;/h2&gt;

&lt;p&gt;It's good to know what Airflow is not the only one on the market. There are &lt;a href="https://www.dagster.io/" rel="noopener noreferrer"&gt;Dagster&lt;/a&gt; and &lt;a href="https://github.com/spotify/luigi" rel="noopener noreferrer"&gt;Spotify Luigi&lt;/a&gt; and others. But they have different pros and cons, be sure that you did a good investigation on the market to choose the best suitable tool for your tasks.&lt;/p&gt;

&lt;p&gt;That's all for today ;) I hope this article will give some clues and basics for them will start working with Airflow and orchestration. Stay tuned!&lt;/p&gt;

</description>
      <category>airflow</category>
      <category>python</category>
      <category>etl</category>
      <category>beginners</category>
    </item>
    <item>
      <title>WebAssembly. How to make the web faster than light</title>
      <dc:creator>Sergii Lischuk</dc:creator>
      <pubDate>Wed, 10 Nov 2021 10:45:49 +0000</pubDate>
      <link>https://dev.to/leefrost/webassembly-how-to-make-the-web-faster-than-light-3bl2</link>
      <guid>https://dev.to/leefrost/webassembly-how-to-make-the-web-faster-than-light-3bl2</guid>
      <description>&lt;p&gt;Today is very important to work with the information in fast and understandable manner. If in case of desktop application situation is fine with it, in case of Web we get some troubles - all data are under control of JS, which is fast but not in the top of the performance charts. Here, on the scene, we meet WebAssembly.&lt;/p&gt;

&lt;h3&gt;
  
  
  The future is coming.
&lt;/h3&gt;

&lt;p&gt;Evolution is everywhere. Even in web stack, there are changes which were made to update the current status of development up to the new edge. We are involved in this process not only as spectators but as an essential users - we got async/await, promises, iterators, etc. Now, from March 2017 (for Chrome) we can use WebAssembly directly in our web apps. But let’s start from the beginning - "Why?", "What?" and "How?" are our best friends in our way as WebAssembly ambassadors.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is WebAssembly?
&lt;/h3&gt;

&lt;p&gt;WebAssembly (WASM) - its a new binary format which allows us to run our code directly in our browsers.&lt;/p&gt;

&lt;h4&gt;
  
  
  Problem
&lt;/h4&gt;

&lt;p&gt;Why it was invented and what are the problems that was solved by WASM? In general - our code should be faster in our browsers. But it is not a full problem - it consists of next sub-problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Our code should be faster for JS (almost like a native code in CPU);&lt;/li&gt;
&lt;li&gt;Zero configuration - solution should be “out of the box” - no special installations, the only browser required;&lt;/li&gt;
&lt;li&gt;Security - new technology should be safe and run inside sandbox
Cross-platform - desktop, mobile, tablet;&lt;/li&gt;
&lt;li&gt;Easy to use and develop;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  What is wrong with JS?
&lt;/h4&gt;

&lt;p&gt;Nothing. But due to its design, it is not possible to make it faster. A long way of development and combination of interpreter and compiler at runtime makes JS ‘hardly predictable’ in execution. &lt;/p&gt;

&lt;p&gt;For example, you have a function &lt;code&gt;foo(a, b)&lt;/code&gt;. And you run this function a lot of times only with numbers. After some time of execution, interpreter push this code to the compiler, and the compiler provides machine code, which is super fast for calculation. But! If you pass a string as parameter to &lt;code&gt;foo(a, b)&lt;/code&gt;, an engine will make ‘de-optimization’: this function will be shifted back to an interpreter and ready-state machine code will be thrown away.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fly4z7o3ehodljxejztun.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fly4z7o3ehodljxejztun.png" alt="JS runtime"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How WebAssembly will help us?
&lt;/h3&gt;

&lt;p&gt;If web app performance is our main goal then we are speaking about code optimizations. If it is not enough, and we are limited by JS engine, we should move code responsible for the high-pressure operation to the WASM module. We re-write this code part to C or Rust and after compilation, we will get some &lt;em&gt;.wasm&lt;/em&gt; file. This file we will leave on the server and provide access to it from the browser. “Ok. But how it will work in browser?” — right question now. Next, inside our JS code, we request this module from the server. When it will be loaded and available, JS engine will call methods from &lt;em&gt;.wasm&lt;/em&gt; as well as the functions from other modules. The code in this &lt;em&gt;.wasm&lt;/em&gt; module will be executed in its own sandbox and result will be returned back to JS. &lt;/p&gt;

&lt;p&gt;We can think about the WASM like about native modules in JS — but in this case code inside WASM module is executed not in JS engine.&lt;/p&gt;

&lt;p&gt;WASM has some restrictions — it is only can be accessible via JS. So, here is a bottleneck — heavyweight operations will be executed faster, but we got some costs for passing and receiving data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusions
&lt;/h3&gt;

&lt;p&gt;WASM is aimed to fix troubles, described above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Speed: WASM executed almost with the speed of machine code on the CPU;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Effectively: binary format, fast parsing, and compilation. All heavyweight operation will be hidden in WASM module;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Security: sandbox model of execution;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An open standard: WASM has its own format and specification. They are available with RFC on the Internet;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The code, inside of the module can be debugged natively from the browser console.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On my opinion WASM is the great feature. With smart usage, working with complicated calculation will be painless for us and for the browser as well. So, apps, which are working with Graphics or CV becomes a native part of the web - and it is really cool news.&lt;/p&gt;

&lt;p&gt;Stay tuned!&lt;/p&gt;

</description>
      <category>webassembly</category>
      <category>javascript</category>
      <category>beginners</category>
    </item>
    <item>
      <title>7 best practices for building containers</title>
      <dc:creator>Sergii Lischuk</dc:creator>
      <pubDate>Thu, 04 Nov 2021 15:24:38 +0000</pubDate>
      <link>https://dev.to/leefrost/7-best-practices-for-building-containers-2lf7</link>
      <guid>https://dev.to/leefrost/7-best-practices-for-building-containers-2lf7</guid>
      <description>&lt;p&gt;Development was always a way of evolution. The evolution of modern programming development brings a lot of techniques and requirements - its hard to imagine today’s programming without high-level frameworks, containers, cloud computing or special data storages (even if they are not necessary). Working with some of them, I would like to share small notes about the containerization, especially with Docker containers.&lt;/p&gt;

&lt;h2&gt;
  
  
  7 best practices for building containers
&lt;/h2&gt;

&lt;p&gt;Kubernetes Engine is a great place to run your workloads at scale. But before being able to use Kubernetes, you need to containerize your applications. You can run most applications in a Docker container without too much hassle. However, effectively running those containers in production and streamlining the build process is another story. There are a number of things to watch out for that will make your security and operations teams happier. This post provides tips and best practices to help you effectively build containers.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Package a single application per container
&lt;/h3&gt;

&lt;p&gt;A container works best when a single application runs inside it. This application should have a single parent process. For example, do not run PHP and MySQL in the same container: it’s harder to debug, Linux signals will not be properly handled, you can’t horizontally scale the PHP containers, etc. This allows you to tie together the lifecycle of the application to that of the container.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Properly handle PID 1, signal handling, and zombie processes
&lt;/h3&gt;

&lt;p&gt;Kubernetes and Docker send Linux signals to your application inside the container to stop it. They send those signals to the process with the process identifier (PID) 1. If you want your application to stop gracefully when needed, you need to properly handle those signals.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Optimize for the Docker build cache
&lt;/h3&gt;

&lt;p&gt;Docker can cache layers of your images to accelerate later builds. This is a very useful feature, but it introduces some behaviors that you need to take into account when writing your Dockerfiles. For example, you should add the source code of your application as late as possible in your Dockerfile so that the base image and your application’s dependencies get cached and aren’t rebuilt on every build.&lt;/p&gt;

&lt;p&gt;Take this Dockerfile as example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.5&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; my_code src&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;my_requirements
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should swap the last two lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.5&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;my_requirements
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; my_code src&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the new version, the result of the pip command will be cached and will not be rerun each time the source code changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Remove unnecessary tools
&lt;/h3&gt;

&lt;p&gt;Reducing the attack surface of your host system is always a good idea, and it’s much easier to do with containers than with traditional systems. Remove everything that the application doesn’t need from your container. Or better yet, include just your application in a "distroless" or scratch image. You should also, if possible, make the filesystem of the container read-only. This should get you some excellent feedback from your security team during your performance review.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Build the smallest image possible
&lt;/h3&gt;

&lt;p&gt;Who likes to download hundreds of megabytes of useless data? Aim to have the smallest images possible. This decreases download times, cold start times, and disk usage. You can use several strategies to achieve that: start with a minimal base image, leverage common layers between images and make use of Docker’s multi-stage build feature.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Properly tag your images
&lt;/h3&gt;

&lt;p&gt;Tags are how the users choose which version of your image they want to use. There are two main ways to tag your images: Semantic Versioning, or using the Git commit hash of your application. Whichever your choose, document it and clearly set the expectations that the users of the image should have. Be careful: while users expect some tags —like the “latest” tag— to move from one image to another, they expect other tags to be immutable, even if they are not technically so. For example, once you have tagged a specific version of your image, with something like “1.2.3”, you should never move this tag.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Carefully consider whether to use a public image
&lt;/h3&gt;

&lt;p&gt;Using public images can be a great way to start working with a particular piece of software. However, using them in production can come with a set of challenges, especially in a high-constraint environment. You might need to control what’s inside them, or you might not want to depend on an external repository, for example. On the other hand, building your own images for every piece of software you use is not trivial, particularly because you need to keep up with the security updates of the upstream software. Carefully weigh the pros and cons of each for your particular use-case, and make a conscious decision.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
