<?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>Forem: Ivan Zykov</title>
    <description>The latest articles on Forem by Ivan Zykov (@net0pyr).</description>
    <link>https://forem.com/net0pyr</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%2F3684134%2Fa45bbafd-37c5-4702-89e7-957cf7799fa2.jpg</url>
      <title>Forem: Ivan Zykov</title>
      <link>https://forem.com/net0pyr</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/net0pyr"/>
    <language>en</language>
    <item>
      <title>I Squeezed an Entire MLOps Pipeline into 10 Lines of YAML</title>
      <dc:creator>Ivan Zykov</dc:creator>
      <pubDate>Tue, 31 Mar 2026 11:14:49 +0000</pubDate>
      <link>https://forem.com/net0pyr/i-squeezed-an-entire-mlops-pipeline-into-10-lines-of-yaml-lph</link>
      <guid>https://forem.com/net0pyr/i-squeezed-an-entire-mlops-pipeline-into-10-lines-of-yaml-lph</guid>
      <description>&lt;p&gt;Every ML project I worked on in GitLab had the same problem: a bloated &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; with hand-rolled MLflow integration, custom validation scripts, and manual model registration. Copy it to the next project, tweak the paths, fix the bugs you already fixed last time. By the fifth project you don't remember which config has the working version of &lt;code&gt;MLFLOW_RUN_ID&lt;/code&gt; passthrough between jobs.&lt;/p&gt;

&lt;p&gt;So I built a &lt;a href="https://gitlab.com/netOpyr/gitlab-mlops-component" rel="noopener noreferrer"&gt;GitLab CI/CD component&lt;/a&gt; that replaces all of that with 10 lines of YAML.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before vs After
&lt;/h2&gt;

&lt;p&gt;Here's what a typical MLOps pipeline looked like before — and this is the shortened version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;validate&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;train&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;evaluate&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;register&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;validate-data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;validate&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python:3.12&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pip install pandas great_expectations&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;python scripts/validate.py --data data/train.csv --check-nulls --threshold &lt;/span&gt;&lt;span class="m"&gt;0.05&lt;/span&gt;
  &lt;span class="na"&gt;artifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;validation_report.json&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;train-model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;train&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python:3.12&lt;/span&gt;
  &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;MLFLOW_TRACKING_URI&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/ml/mlflow"&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pip install mlflow scikit-learn pandas&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;python scripts/train.py --data data/train.csv&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo "MLFLOW_RUN_ID=$(cat run_id.txt)" &amp;gt;&amp;gt; train.env&lt;/span&gt;
  &lt;span class="na"&gt;artifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;reports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;dotenv&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;train.env&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;model/&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;metrics.json&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;evaluate-model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;evaluate&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python:3.12&lt;/span&gt;
  &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[{&lt;/span&gt;&lt;span class="nv"&gt;job&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;train-model&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;artifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;true&lt;/span&gt;&lt;span class="pi"&gt;}]&lt;/span&gt;
  &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;MLFLOW_TRACKING_URI&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/ml/mlflow"&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pip install mlflow&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;python scripts/evaluate.py --run-id $MLFLOW_RUN_ID --threshold &lt;/span&gt;&lt;span class="m"&gt;0.85&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo "EVAL_PASSED=$(cat eval_result.txt)" &amp;gt;&amp;gt; evaluate.env&lt;/span&gt;
  &lt;span class="na"&gt;artifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;reports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;dotenv&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;evaluate.env&lt;/span&gt;

&lt;span class="na"&gt;register-model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;register&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python:3.12&lt;/span&gt;
  &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[{&lt;/span&gt;&lt;span class="nv"&gt;job&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;train-model&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;artifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;true&lt;/span&gt;&lt;span class="pi"&gt;},&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;job&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;evaluate-model&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;artifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;true&lt;/span&gt;&lt;span class="pi"&gt;}]&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$EVAL_PASSED == "true"&lt;/span&gt;
  &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;MLFLOW_TRACKING_URI&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/ml/mlflow"&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pip install mlflow&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;python scripts/register.py --run-id $MLFLOW_RUN_ID --model-name my-model&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And this doesn't even include DVC, pip caching, retry logic, or error handling. Each project also had its own &lt;code&gt;validate.py&lt;/code&gt;, &lt;code&gt;evaluate.py&lt;/code&gt;, &lt;code&gt;register.py&lt;/code&gt; — each with its own implementation of &lt;code&gt;auto_configure_mlflow&lt;/code&gt;, its own argument parsing, its own bugs.&lt;/p&gt;

&lt;p&gt;Now the same thing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;validate&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;train&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;evaluate&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;register&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gitlab.com/netOpyr/gitlab-mlops-component/full-pipeline@1.0.0&lt;/span&gt;
    &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;model_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wine-classifier&lt;/span&gt;
      &lt;span class="na"&gt;training_script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scripts/train.py&lt;/span&gt;
      &lt;span class="na"&gt;training_args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--data&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;data/train.csv&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--test-data&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;data/test.csv'&lt;/span&gt;
      &lt;span class="na"&gt;data_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;data/train.csv&lt;/span&gt;
      &lt;span class="na"&gt;framework&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sklearn&lt;/span&gt;
      &lt;span class="na"&gt;metric_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;accuracy&lt;/span&gt;
      &lt;span class="na"&gt;min_threshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.85'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These 10 lines give you 4 jobs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;validate --&amp;gt; train --&amp;gt; evaluate --&amp;gt; register
   |           |          |            |
 schema      MLflow    accuracy    Model Registry
 nulls       autolog   &amp;gt;= 0.85    (if eval passed)
 drift       metrics   vs prod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All the boilerplate scripts now live inside the component. You only write the training script.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Each Stage Does
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;validate&lt;/strong&gt; checks your data before training starts: schema validation (are all columns present?), null ratio per column (default threshold 5%), and optionally data drift detection via Evidently. Supports Great Expectations suites and custom Python check scripts too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;train&lt;/strong&gt; wraps your training script in an MLflow session. It auto-configures the tracking URI from GitLab CI variables, creates an experiment and run, enables autolog for your framework (sklearn, PyTorch, TensorFlow, XGBoost, LightGBM), and passes &lt;code&gt;MLFLOW_RUN_ID&lt;/code&gt; to your script via environment variable. Your script stays a normal Python file — it works locally and in Jupyter just the same.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;evaluate&lt;/strong&gt; pulls metrics from MLflow and runs them through quality gates. Gate 1: absolute threshold (e.g. &lt;code&gt;accuracy &amp;gt;= 0.85&lt;/code&gt;). Gate 2 (optional): comparison with the current production model from Model Registry. Supports &lt;code&gt;higher_is_better: false&lt;/code&gt; for loss metrics.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;register&lt;/strong&gt; pushes the model to GitLab Model Registry with metadata: alias (&lt;code&gt;staging&lt;/code&gt; by default), commit SHA, pipeline ID, metrics. Works on all GitLab tiers — on Free, alias assignment silently falls back to tags.&lt;/p&gt;

&lt;h2&gt;
  
  
  DVC Integration
&lt;/h2&gt;

&lt;p&gt;If your data lives in S3/MinIO:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.../train@1.0.0&lt;/span&gt;
    &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;training_script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scripts/train.py&lt;/span&gt;
      &lt;span class="na"&gt;model_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-model&lt;/span&gt;
      &lt;span class="na"&gt;dvc_enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;dvc_remote&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;minio&lt;/span&gt;
      &lt;span class="na"&gt;dvc_files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;data/train.csv.dvc&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;data/test.csv.dvc'&lt;/span&gt;
      &lt;span class="na"&gt;dvc_push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;dvc_push_paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;model/'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The component installs DVC, pulls data before training, and pushes artifacts back after. Credentials go through CI/CD variables: &lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt;, &lt;code&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt;, &lt;code&gt;AWS_ENDPOINT_URL&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  When 10 Lines Aren't Enough
&lt;/h2&gt;

&lt;p&gt;For more complex setups you can include each stage separately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.../validate@1.0.0&lt;/span&gt;
    &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;data_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;data/train.csv&lt;/span&gt;
      &lt;span class="na"&gt;enable_drift&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;reference_data_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;data/reference.csv&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.../train@1.0.0&lt;/span&gt;
    &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;training_script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scripts/train.py&lt;/span&gt;
      &lt;span class="na"&gt;model_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-model&lt;/span&gt;
      &lt;span class="na"&gt;image_suffix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pytorch-gpu&lt;/span&gt;
      &lt;span class="na"&gt;framework&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pytorch&lt;/span&gt;
      &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpu"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.../evaluate@1.0.0&lt;/span&gt;
    &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;model_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-model&lt;/span&gt;
      &lt;span class="na"&gt;metric_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;val_loss&lt;/span&gt;
      &lt;span class="na"&gt;min_threshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.1'&lt;/span&gt;
      &lt;span class="na"&gt;higher_is_better&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.../register@1.0.0&lt;/span&gt;
    &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;model_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-model&lt;/span&gt;
      &lt;span class="na"&gt;alias&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;staging&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way you get per-stage GPU runners, custom images, and conditional execution. You can also train multiple models in parallel using the &lt;code&gt;as&lt;/code&gt; parameter to give unique names to jobs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Framework Support
&lt;/h2&gt;

&lt;p&gt;Each framework has a dedicated Docker image selected via &lt;code&gt;image_suffix&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Suffix&lt;/th&gt;
&lt;th&gt;Frameworks&lt;/th&gt;
&lt;th&gt;GPU&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sklearn&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;scikit-learn, matplotlib&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;boosting&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;XGBoost, LightGBM, scikit-learn&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pytorch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;PyTorch (CPU)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pytorch-gpu&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;PyTorch + CUDA 12.4&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tensorflow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;TensorFlow (CPU)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tensorflow-gpu&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;TensorFlow + CUDA 12.4&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All images include Python 3.12, MLflow, and pandas. Need extra dependencies? Set &lt;code&gt;requirements_file: requirements.txt&lt;/code&gt; or bring your own image via &lt;code&gt;image_registry_base&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;Two options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fork the &lt;a href="https://gitlab.com/netOpyr/mlops-component-example" rel="noopener noreferrer"&gt;example project&lt;/a&gt; — a wine classifier with 3 files total. Just create an access token with API scope and add it as &lt;code&gt;MLOPS_ACCESS_TOKEN&lt;/code&gt; in CI/CD variables.&lt;/li&gt;
&lt;li&gt;Add the component to your existing project. Drop your training script into &lt;code&gt;scripts/&lt;/code&gt; and configure the inputs.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The component is published in the &lt;a href="https://gitlab.com/netOpyr/gitlab-mlops-component" rel="noopener noreferrer"&gt;GitLab CI/CD Catalog&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Coming soon: BuildKit-based image builds, retry logic for flaky MLflow requests, and GitLab Environments integration.&lt;/p&gt;

&lt;p&gt;Found a bug or missing a feature? &lt;a href="https://gitlab.com/netOpyr/gitlab-mlops-component" rel="noopener noreferrer"&gt;Open an issue or MR&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/netOpyr/gitlab-mlops-component" rel="noopener noreferrer"&gt;Component&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/netOpyr/mlops-component-example" rel="noopener noreferrer"&gt;Example project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.gitlab.com/ci/components/" rel="noopener noreferrer"&gt;GitLab CI/CD Components docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>mlops</category>
      <category>gitlab</category>
      <category>cicd</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>My first container without Docker</title>
      <dc:creator>Ivan Zykov</dc:creator>
      <pubDate>Mon, 29 Dec 2025 13:43:06 +0000</pubDate>
      <link>https://forem.com/net0pyr/my-first-container-without-docker-63n</link>
      <guid>https://forem.com/net0pyr/my-first-container-without-docker-63n</guid>
      <description>&lt;p&gt;Containerization technologies, perhaps like most readers of this article, are stuck in my mind. And it would seem, just write Dockerfile and don't show off. But you always want to learn something new and delve deeper into topics you've already mastered. For this reason, I decided to figure out how containers are implemented in Linux-based systems and then create my own "container" using cmd.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who maintains containers in Linux?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fjf5y52u07ivogikxw2nl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fjf5y52u07ivogikxw2nl.png" alt="On guard" width="800" height="800"&gt;&lt;/a&gt;&lt;br&gt;
First, you need to understand what containerization technology is based on. There are two mechanisms in the Linux kernel: namespace and cgroups (control groups). They provide the isolation and scalability that we all love about containers. Let's take a look at both mechanisms in order.&lt;/p&gt;
&lt;h3&gt;
  
  
  Namespace
&lt;/h3&gt;

&lt;p&gt;Namespaces allow us to isolate system resources between processes. With their help, we can create a separate virtual system while formally remaining in the host system. Perhaps this brief explanation has not enlightened you much, so let's look at an example:&lt;br&gt;
Let's consider a container raised from the alpine image. Let's start it and the interactive shell in it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-it&lt;/span&gt; alpine /bin/sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's create a new process in the container and check the output of the ps command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sleep &lt;/span&gt;1000 &amp;amp;
ps &lt;span class="nt"&gt;-a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Получаем:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PID   USER     TIME  COMMAND
    1 root      0:00 /bin/sh
   29 root      0:00 sleep 1000
   30 root      0:00 ps -a
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the process PID is 29. Now let's try to find the same process, but on the host machine. To do this, we will determine the container ID and use the command to display the processes running inside docker&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker top &amp;lt;container ID&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a result, we get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UID     PID       PPID      C    STIME    TTY      TIME        CMD
root    172147    172124    0    Feb05    pts/0    00:00:00    /bin/sh
root    173602    172147    0    Feb05    pts/0    00:00:00    sleep 1000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's pay attention to two columns: PID and PPID (parent PID). They indicate the PID of the process itself and its parent, but in the host system. Let's check it out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ps aux | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'173602|172147'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root      172147  0.0  0.0   1736   908 pts/0    Ss+  Feb05   0:00 /bin/sh
root      173602  0.0  0.0   1624   980 pts/0    S    Feb05   0:00 sleep 1000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which is exactly what we needed to prove! To sum up, we can conclude that the container knows nothing about the host machine. It considers itself to be an independent system. However, in reality, all processes are run on the host, they are simply located in the namespace of the container. This creates the illusion of a separate, independent system. &lt;br&gt;
I hope this example has clarified the situation with namespaces a little. In it, we looked at one of the eight types of namespaces. Now I would like to briefly go over each one:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;Mount&lt;/code&gt; - isolation of file system mount points. Allows you to set your own file system hierarchy;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;UTS&lt;/code&gt; - host name isolation. Allows each container to specify its own host name;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PID&lt;/code&gt; - process ID isolation. Allows you to create a separate process tree;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Network&lt;/code&gt; - isolation of network interfaces and routing tables;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;IPC&lt;/code&gt; - IPC (interprocess communication) isolation.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;User&lt;/code&gt; - system user isolation. Allows you to create separate users for each container, including root.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Cgroup&lt;/code&gt; - cgroup access isolation. Allows you to limit container resources and prevents interference from other containers.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Time&lt;/code&gt; - system time isolation&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To create a new namespace in Linux, there is a command called unshare. We will take a closer look at it a little later.&lt;/p&gt;
&lt;h3&gt;
  
  
  Cgroups
&lt;/h3&gt;

&lt;p&gt;Control groups are a Linux kernel mechanism that allows you to manage process resources. With its help, you can limit and isolate the use of CPU, memory, network, and disk resources.&lt;br&gt;
There are two versions of cgoups: v1 and v2. In most modern systems, you will encounter the second version, which is used in systemd. The main difference between the versions is in the construction of the constraint tree. In the first version, nodes were created for each type of constraint, and groups were added to them. In the second version, each group has its own node, which contains all the necessary constraints. To better understand this, let's take a look at the visualization of the v1 and v2 trees:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#v1
/sys/fs/cgroup/
├── cpu
│   ├── group1/
│   │   ├── tasks
│   │   ├── cgroup.procs
│   │   ├── cpu.shares
│   │   └── ...
│   ├── group2/
│   │   ├── tasks
│   │   ├── cgroup.procs
│   │   ├── cpu.shares
│   │   └── ...
│   └── ...
├── memory
│   ├── group1/
│   │   ├── tasks
│   │   ├── cgroup.procs
│   │   ├── memory.limit_in_bytes
│   │   └── ...
│   ├── group2/
│   │   ├── tasks
│   │   ├── cgroup.procs
│   │   ├── memory.limit_in_bytes
│   │   └── ...
│   └── ...
└── ...

#v2
/sys/fs/cgroup/
├── group1/
│   ├── cgroup.procs
│   ├── cpu.max
│   ├── cpu.weight
│   ├── memory.current
│   ├── memory.max
│   └── ...
├── group2/
│   ├── cgroup.procs
│   ├── cpu.max
│   ├── cpu.weight
│   ├── memory.current
│   ├── memory.max
│   └── ...
└── ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's take a look at how cgroups work using the example of a Docker container. First, let's start the container, limiting its resources (2 cores and 512 MB):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--cpus&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"2"&lt;/span&gt; &lt;span class="nt"&gt;--memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"512m"&lt;/span&gt; nginx 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we will find a group for this container using the &lt;code&gt;find&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;find /sys/fs/cgroup &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'*&amp;lt;container ID&amp;gt;*'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, let's check the contents of the &lt;code&gt;cpu.max&lt;/code&gt; and &lt;code&gt;memory.max&lt;/code&gt; files in the directory we found:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# cpu.max
200000 100000

# memory.max
536870912
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which is what needed to be proven! &lt;/p&gt;

&lt;h2&gt;
  
  
  Создание контейнера без docker
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fx8qj8c8rktkb12i5olx4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fx8qj8c8rktkb12i5olx4.png" alt="Is Tux a wizard?!" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We have covered the basic theory we need. Now let's move on to practice and resort to the magic of the command line.&lt;br&gt;
First, let's create the container's file system structure and install &lt;code&gt;busybox&lt;/code&gt; in the &lt;code&gt;/bin&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create the root directory of the container and navigate to it.&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; ~/container &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; ~/container
&lt;span class="c"&gt;# Create the main system directories and navigate to /bin.&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ./&lt;span class="o"&gt;{&lt;/span&gt;proc,sys,dev,tmp,bin,root,etc&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;bin
&lt;span class="c"&gt;# Install busybox.&lt;/span&gt;
wget https://www.busybox.net/downloads/binaries/1.35.0-x86_64-linux-musl/busybox
&lt;span class="c"&gt;# Grant execution rights&lt;/span&gt;
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x busybox
&lt;span class="c"&gt;# Create symlinks for all commands available in busybox &lt;/span&gt;
./busybox &lt;span class="nt"&gt;--list&lt;/span&gt; | xargs &lt;span class="nt"&gt;-I&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt; &lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; busybox &lt;span class="o"&gt;{}&lt;/span&gt;
&lt;span class="c"&gt;# Return to the root directory of the container&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/container
&lt;span class="c"&gt;# Add the PATH variable to the /etc/profile file&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; ‘export &lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/bin’ &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/container/etc/profile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will also add to the &lt;code&gt;/etc/passwd&lt;/code&gt; and &lt;code&gt;/etc/group&lt;/code&gt; files so that we are root within the isolated system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"root:x:0:0:root:/root:/bin/sh"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/container/etc/passwd
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"root:x:0:"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/container/etc/group
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we will mount the system directories:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Mount devices using existing ones&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;mount &lt;span class="nt"&gt;--bind&lt;/span&gt; /dev ~/container/dev
&lt;span class="c"&gt;# Mount processes&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;mount &lt;span class="nt"&gt;-t&lt;/span&gt; proc none ~/container/proc
&lt;span class="c"&gt;# Mount the sysfs file system&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;mount &lt;span class="nt"&gt;-t&lt;/span&gt; sysfs none ~/container/sys
&lt;span class="c"&gt;# Mount the tmpfs file system&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;mount &lt;span class="nt"&gt;-t&lt;/span&gt; tmpfs none ~/container/tmp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;!!!Note: To unmount later, you can use the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;umount ~/container/&lt;span class="o"&gt;{&lt;/span&gt;proc,sys,dev,tmp&lt;span class="o"&gt;}&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have prepared the file system for our container. Now let's move on to creating isolation. To do this, we will use the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;unshare &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="nt"&gt;-U&lt;/span&gt; &lt;span class="nt"&gt;--map-root-user&lt;/span&gt; &lt;span class="nt"&gt;--mount-proc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./proc &lt;span class="se"&gt;\&lt;/span&gt;
    /bin/chroot ~/container /bin/sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"source /etc/profile &amp;amp;&amp;amp; exec /bin/sh"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's take a closer look at it:&lt;br&gt;
-f - fork. Create a new process to isolate it from the parent process.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-p&lt;/code&gt; - PID namespace;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-m&lt;/code&gt; - mount namespace;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-n&lt;/code&gt; - Network namespace;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-i&lt;/code&gt; - IPC namespace;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-u&lt;/code&gt; - UTS namespace;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-U&lt;/code&gt; - User namespace;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--map-root-user&lt;/code&gt; - map the active user's uid and gid to root inside the container;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-mount-proc&lt;/code&gt; - mount proc inside the container;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/bin/chroot ~/container&lt;/code&gt; - change the root directory;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/bin/sh -c “source /etc/profile &amp;amp;&amp;amp; exec /bin/sh”&lt;/code&gt; - start the shell and execute the command that will apply the /etc/profile file and start an interactive shell. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Great! We got our container. Now we need to limit resources. To do this, we will open a new session on the host and perform a series of actions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a new group. My system uses cgroups v2, so&lt;/span&gt;
&lt;span class="c"&gt;# the directory will be automatically configured to work with resources.&lt;/span&gt;
&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; /sys/fs/cgroup/my_container
&lt;span class="c"&gt;# Write a limit of 2 processor cores&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; “200000 100000” | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /sys/fs/cgroup/my_container/cpu.max
&lt;span class="c"&gt;# Allocate a maximum of 512MB of memory&lt;/span&gt;
&lt;span class="nb"&gt;echo &lt;/span&gt;536870912 | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /sys/fs/cgroup/my_container/memory.max
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we need to determine the PID of the container. To do this, we will use the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ps aux | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'/bin/sh$'&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Take the PID from the second column and add it to the cgroup.procs file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &amp;lt;PID&amp;gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /sys/fs/cgroup/my_container/cgroup.procs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That completes the basic configuration. We have created an isolated system and added resource restrictions. But we would like to make it a little more functional, so let's set up a virtual network between the host and the container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a pair of virtual interfaces&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;ip &lt;span class="nb"&gt;link &lt;/span&gt;add veth-host &lt;span class="nb"&gt;type &lt;/span&gt;veth peer name veth-container
&lt;span class="c"&gt;# Bring up the interface on the host&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;ip &lt;span class="nb"&gt;link set &lt;/span&gt;veth-host up
&lt;span class="c"&gt;# Assign any free address in your network to the host interface&lt;/span&gt;
&lt;span class="c"&gt;# I am using 192.168.1.123/24&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;ip addr add 192.168.1.123/24 dev veth-host
&lt;span class="c"&gt;# Move veth-container to the container namespace&lt;/span&gt;
&lt;span class="c"&gt;# Here you need to specify the PID of the container you used before&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;ip &lt;span class="nb"&gt;link set &lt;/span&gt;veth-container netns &amp;lt;PID&amp;gt;
&lt;span class="c"&gt;# Bring up the interface inside the container&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;nsenter &lt;span class="nt"&gt;--net&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/proc/&amp;lt;PID&amp;gt;/ns/net ip &lt;span class="nb"&gt;link set &lt;/span&gt;veth-container up
&lt;span class="c"&gt;# Assign any free address on your network to the container interface&lt;/span&gt;
&lt;span class="c"&gt;# I am using 192.168.1.124/24&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;nsenter &lt;span class="nt"&gt;--net&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/proc/&amp;lt;PID&amp;gt;/ns/net ip addr add 192.168.1.124/24 dev veth-container
&lt;span class="c"&gt;# Configure the default gateway for traffic routing&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;nsenter &lt;span class="nt"&gt;--net&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/proc/&amp;lt;PID&amp;gt;/ns/net ip route add default via 192.168.1.123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have raised all the necessary interfaces. Now we need to configure routing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Allow packet forwarding&lt;/span&gt;
&lt;span class="nb"&gt;echo &lt;/span&gt;1 | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /proc/sys/net/ipv4/ip_forward
&lt;span class="c"&gt;# Add a NAT rule for masquerading outgoing packets from the network &lt;/span&gt;
&lt;span class="c"&gt;# 192.168.1.0/24 through the interface that faces the external network. For me, this is enp3s0.&lt;/span&gt;
&lt;span class="c"&gt;# Masquerading masks packets leaving the container so that they look&lt;/span&gt;
&lt;span class="c"&gt;# like packets sent from the host&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;iptables &lt;span class="nt"&gt;-t&lt;/span&gt; nat &lt;span class="nt"&gt;-A&lt;/span&gt; POSTROUTING &lt;span class="nt"&gt;-s&lt;/span&gt; 192.168.1.0/24 &lt;span class="nt"&gt;-o&lt;/span&gt; enp3s0 &lt;span class="nt"&gt;-j&lt;/span&gt; MASQUERADE
&lt;span class="c"&gt;# Add a rule to allow packet forwarding&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;iptables &lt;span class="nt"&gt;-A&lt;/span&gt; FORWARD &lt;span class="nt"&gt;-s&lt;/span&gt; 192.168.1.0/24 &lt;span class="nt"&gt;-o&lt;/span&gt; enp3s0 &lt;span class="nt"&gt;-j&lt;/span&gt; ACCEPT
&lt;span class="c"&gt;# Add a rule to allow incoming packets&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;iptables &lt;span class="nt"&gt;-A&lt;/span&gt; FORWARD &lt;span class="nt"&gt;-d&lt;/span&gt; 192.168.1.0/24 &lt;span class="nt"&gt;-m&lt;/span&gt; state &lt;span class="nt"&gt;--state&lt;/span&gt; RELATED,ESTABLISHED &lt;span class="nt"&gt;-j&lt;/span&gt; ACCEPT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great! We've created our first container. Obviously, there's still a lot that can be configured, such as DNS, which isn't working right now. But that's up to each individual to decide how to deal with it.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>containers</category>
      <category>linux</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
