<?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: Raül Martínez i Peris</title>
    <description>The latest articles on DEV Community by Raül Martínez i Peris (@elferrer).</description>
    <link>https://dev.to/elferrer</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%2F1152132%2F7c1d1879-43bc-4c11-ab04-531876f3e56d.jpg</url>
      <title>DEV Community: Raül Martínez i Peris</title>
      <link>https://dev.to/elferrer</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/elferrer"/>
    <language>en</language>
    <item>
      <title>ROCm</title>
      <dc:creator>Raül Martínez i Peris</dc:creator>
      <pubDate>Wed, 31 Dec 2025 14:12:19 +0000</pubDate>
      <link>https://dev.to/elferrer/rocm-not-cuda-3c7d</link>
      <guid>https://dev.to/elferrer/rocm-not-cuda-3c7d</guid>
      <description>&lt;p&gt;NOTA: Este documento está basado en Ubuntu.&lt;/p&gt;

&lt;p&gt;¿Todavía no sabes que es ROCm?&lt;/p&gt;

&lt;p&gt;ROCm es la alternativa libre a CUDA. Es cierto que CUDA le lleva ventaja, pero, las cosas empiezan a cambiar, sobre todo debido a la deriva del mercado... Nvidia no puede "cubrir" la demanda mundial y ello a dado una ventana de oportunidad a que AMD pueda convencer.&lt;/p&gt;

&lt;p&gt;ROCm son drivers libres para las GPU, aunque es evidente que al estar bajo el paraguas de AMD es sobretodo para AMDGPUs: &lt;a href="https://rocm.docs.amd.com/en/latest/index.html" rel="noopener noreferrer"&gt;https://rocm.docs.amd.com/en/latest/index.html&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  I. Diagnosticar tu AMDGPU
&lt;/h2&gt;

&lt;p&gt;Lo primero es instalar las herramientas de diagnóstico necesarias:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt update &amp;amp;&amp;amp; sudo apt install -y lshw
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y tras ello, ejecutamos el diagnóstico:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo lshw -c video 2&amp;gt;/dev/null | grep "configuration"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para la validación del resultado debes buscar la cadena &lt;code&gt;driver=amdgpu&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;configuration: driver=amdgpu latency=0&lt;/code&gt;: El sistema gráfico está correctamente anclado al driver nativo.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;configuration: driver=llvmpipe ...&lt;/code&gt; (O salida vacía / driver=unsigned): el sistema no está usando aceleración hardware.&lt;/p&gt;

&lt;h2&gt;
  
  
  Instalar/restaurar drivers nativos
&lt;/h2&gt;

&lt;p&gt;A) Eliminar completamente instalaciones propietarias anteriores:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo amdgpu-install --uninstall 2&amp;gt;/dev/null
sudo apt purge -y "amdgpu-*" "rocm-*" "hsa-*"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;B) Forzar la reinstalación del stack gráfico nativo de Ubuntu:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt update
sudo apt install --reinstall xserver-xorg-video-amdgpu xserver-xorg-core libgl1-mesa-dri
sudo apt install --reinstall linux-image-generic linux-headers-generic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;C) Regenerar imagen de arranque y reiniciar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo update-initramfs -u
sudo reboot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  II. Configuración
&lt;/h2&gt;

&lt;p&gt;Vamos a realizar la configuración para una iGPU Radeon, más exactamente configuramos un equipo con Ryzen AI 9 y Radeon 780M/880M.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt update &amp;amp;&amp;amp; sudo apt install -y build-essential git curl cmake

UBUNTU_CODENAME=$(lsb_release -sc)
BASE_URL="https://repo.radeon.com/amdgpu-install/7.1.1/ubuntu/$UBUNTU_CODENAME/"

INSTALLER_FILE=$(curl -s "$BASE_URL" | grep -o 'amdgpu-install_[0-9.-]*_all.deb' | sort -V | tail -n 1)

if [ -z "$INSTALLER_FILE" ]; then
    echo "Error Crítico: No se localizó el paquete instalador en $BASE_URL"
    exit 1
fi

wget -c "$BASE_URL$INSTALLER_FILE"

sudo apt install -y "./$INSTALLER_FILE"

# Desplegando pila ROCm y HIP (User-space).
# El flag --no-dkms evita la recompilación del módulo del kernel, previniendo conflictos gráficos.
sudo amdgpu-install --usecase=rocm,hip --no-dkms -y

sudo usermod -aG render,video $USER

# Inyección de variable de entorno para compatibilidad con RDNA 3.5
if ! grep -q "HSA_OVERRIDE_GFX_VERSION=11.0.0" ~/.bashrc; then
    echo 'export HSA_OVERRIDE_GFX_VERSION=11.0.0' &amp;gt;&amp;gt; ~/.bashrc
fi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  III. Comprobaciones y tests
&lt;/h2&gt;

&lt;p&gt;Primero revisemos que tenemos la GPU:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lspci | grep -i amd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deberías ver tu gráfica en el listado.&lt;/p&gt;

&lt;p&gt;Si tienes memoria compartida (shared memory) deberás habilitar el máximo que necesites en tu BIOS (UMA Frame Buffer Size o Integrated Graphics Memory).&lt;/p&gt;

&lt;h3&gt;
  
  
  Preparar un contenedor para las comprobaciones.
&lt;/h3&gt;

&lt;p&gt;Crea un &lt;code&gt;Dockerfile&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM rocm/pytorch:rocm6.1_ubuntu22.04_py3.10_pytorch_2.1.2

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update &amp;amp;&amp;amp; apt-get install -y \
    rocm-smi \
    git \
    python3-pip \
    &amp;amp;&amp;amp; rm -rf /var/lib/apt/lists/*

ENV HSA_OVERRIDE_GFX_VERSION=11.0.0

WORKDIR /app

COPY test_gpu.py .

CMD ["/bin/bash", "-c", "python3 test_gpu.py; bash"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;... y para facilitar el uso, también creamos un &lt;code&gt;compose.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;services:
  rocm-strix:
    build: .
    image: rocm-strix:v1
    container_name: rocm_strix_container
    restart: unless-stopped

    # Fundamental para el acceso al hardware
    devices:
      - "/dev/kfd:/dev/kfd"
      - "/dev/dri:/dev/dri"

    # Permisos de seguridad relajados para acceso a memoria GPU
    security_opt:
      - seccomp:unconfined
    cap_add:
      - SYS_PTRACE

    # Grupos necesarios para acceder a video/render
    group_add:
      - video
      - render

    # Vital para PyTorch y sus DataLoaders (memoria compartida)
    ipc: host

    environment:
      # CRÍTICO: Engaña a la librería para usar kernels de RX 7900 en la APU que estoy testeando
      - HSA_OVERRIDE_GFX_VERSION=11.0.0
      - ROCM_PATH=/opt/rocm
      # Opcional: Define la zona horaria
      - TZ=Europe/Madrid 

    volumes:
      - ./data:/app/data
      # --- Acceso al Hardware ---
      - /sys/class/power_supply:/sys/class/power_supply:ro
      - /sys/class/drm:/sys/class/drm:ro
      - /sys/class/hwmon:/sys/class/hwmon:ro
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y por último un pequeño script, &lt;code&gt;test_gpu.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import torch
import time
import psutil
import platform
import os
import subprocess
import json

def format_bytes(size):
    power = 2**10
    n = 0
    power_labels = {0 : 'B', 1: 'KB', 2: 'MB', 3: 'GB', 4: 'TB'}
    while size &amp;gt; power:
        size /= power
        n += 1
    return f"{size:.2f} {power_labels[n]}"

def get_cpu_info():
    try:
        command = "cat /proc/cpuinfo | grep 'model name' | uniq | cut -d : -f 2"
        cpu_name = subprocess.check_output(command, shell=True).decode().strip()
    except:
        cpu_name = platform.processor()

    logical = psutil.cpu_count(logical=True)
    physical = psutil.cpu_count(logical=False)
    freq = psutil.cpu_freq()
    freq_str = f"{freq.max:.0f} Mhz" if freq else "Unknown"
    return cpu_name, physical, logical, freq_str

def get_battery_status():
    if not hasattr(psutil, "sensors_battery"): return "No soportado"
    try:
        battery = psutil.sensors_battery()
        if battery is None: return "No detectada"
        plugged = "CONECTADO" if battery.power_plugged else "BATERÍA"
        return f"{battery.percent}% ({plugged})"
    except: return "Error leyendo batería"

def get_gpu_sensors_rocm():
    data = {}
    try:
        # Intentamos obtener JSON. Si falla, manejamos la excepción.
        raw_json = subprocess.check_output("rocm-smi -a --json", shell=True).decode()
        smi_data = json.loads(raw_json)
        card = list(smi_data.keys())[0]
        gpu_data = smi_data[card]

        # --- LÓGICA DE NOTAS EXPLICATIVAS ---

        # 1. TEMPERATURAS
        edge = gpu_data.get("Temperature (Sensor edge) (C)", "N/A")
        junc = gpu_data.get("Temperature (Sensor junction) (C)", "N/A")
        if junc == "N/A": junc = "N/A (APU: Solo reporta sensor Edge)"
        data['temp'] = f"{edge}°C (Edge) / {junc}"

        # 2. CONSUMO (POWER)
        pwr = gpu_data.get("Average Graphics Package Power (W)", "N/A")
        if pwr == "N/A": pwr = "N/A (APU: Energía compartida CPU/GPU)"
        data['power'] = pwr

        # 3. RELOJES (CLOCKS)
        sclk = gpu_data.get("SCLK", "N/A")
        mclk = gpu_data.get("MCLK", "N/A")
        if sclk == "N/A" or mclk == "N/A":
            # Si Docker bloquea uno, bloquea los dos. Ponemos un mensaje global.
            data['clocks'] = "N/A / N/A (Docker: Ambos sensores (SCLK/MCLK) bloqueados por aislamiento)"
        else:
            data['clocks'] = f"{sclk} / {mclk}"

        # 4. VENTILADOR
        fan = gpu_data.get("Fan Speed (%)", "N/A")
        if fan == "N/A": fan = "N/A (Laptop: Controlado por BIOS/EC)"
        data['fan'] = fan

        # 5. CARGA Y MEMORIA
        data['usage'] = gpu_data.get("GPU use (%)", "N/A")
        data['vram_used'] = gpu_data.get("VRAM Total Used Memory (B)", 0)
        data['gtt_used'] = gpu_data.get("GTT Total Used Memory (B)", 0)

    except Exception as e:
        data['error'] = f"Error interpretando sensores: {str(e)}"
        # Valores por defecto seguros para que no rompa el print
        data['temp'] = "Error"
        data['power'] = "Error"
        data['clocks'] = "Error"
        data['fan'] = "Error"
        data['usage'] = "?"
        data['vram_used'] = 0
        data['gtt_used'] = 0

    return data

def benchmark_latency(iters=1000):
    start = torch.cuda.Event(enable_timing=True)
    end = torch.cuda.Event(enable_timing=True)
    for _ in range(100): torch.cuda.synchronize() # Warmup
    start.record()
    for _ in range(iters): torch.cuda.synchronize()
    end.record()
    torch.cuda.synchronize()
    return (start.elapsed_time(end) / iters) * 1000

def benchmark_bandwidth(size_mb=512):
    elements = size_mb * 1024 * 1024 // 4 
    t_cpu = torch.randn(elements, dtype=torch.float32)
    torch.cuda.synchronize()

    # H2D
    start = time.time()
    t_gpu = t_cpu.to('cuda')
    torch.cuda.synchronize()
    h2d = (size_mb / 1024) / (time.time() - start)

    # D2H
    start = time.time()
    _ = t_gpu.to('cpu')
    torch.cuda.synchronize()
    d2h = (size_mb / 1024) / (time.time() - start)
    return h2d, d2h

def benchmark_compute(n, dtype):
    a = torch.randn(n, n, device="cuda", dtype=dtype)
    b = torch.randn(n, n, device="cuda", dtype=dtype)
    torch.mm(a, b)
    torch.cuda.synchronize()
    start = time.time()
    torch.mm(a, b)
    torch.cuda.synchronize()
    ops = 2 * (n ** 3)
    return (ops / (time.time() - start)) / 1e12

# ================= REPORT =================
print("\n" + "="*60)
print("          ROCm ULTIMATE DIAGNOSTIC TOOL     ")
print("="*60)

# [1] HOST
cpu, _, _, _ = get_cpu_info()
vm = psutil.virtual_memory()
print(f"\n[1] HOST &amp;amp; ENERGÍA")
print(f"CPU:             {cpu}")
print(f"RAM Sistema:     {format_bytes(vm.available)} libres / {format_bytes(vm.total)} Total")
print(f"Estado Energía:  {get_battery_status()}")

# [2] GPU
print(f"\n[2] SENSORES GPU (ROCm SMI)")
if torch.cuda.is_available():
    sensors = get_gpu_sensors_rocm()
    d = 0
    props = torch.cuda.get_device_properties(d)
    print(f"Dispositivo:     {torch.cuda.get_device_name(d)}")
    print(f"VRAM Total:      {format_bytes(props.total_memory)}")
    print(f"-"*40)
    print(f"Temp:            {sensors.get('temp', '?')}")
    print(f"Consumo:         {sensors.get('power', '?')}")
    print(f"Relojes:         {sensors.get('clocks', '?')}")
    print(f"Ventilador:      {sensors.get('fan', '?')}")
    print(f"-"*40)
    print(f"Carga GPU:       {sensors.get('usage', '?')}%")
    print(f"Memoria Usada:   VRAM: {format_bytes(float(sensors.get('vram_used', 0)))} | GTT: {format_bytes(float(sensors.get('gtt_used', 0)))}")
else:
    print("FATAL: GPU NO DETECTADA")

# [3] BENCHMARKS
print(f"\n[3] PRUEBAS DE ESTRÉS &amp;amp; RENDIMIENTO")
print(f"Latencia Kernel: {benchmark_latency():.2f} µs")
try:
    h2d, d2h = benchmark_bandwidth(512)
    print(f"Ancho Banda:     Escritura: {h2d:.2f} GB/s | Lectura: {d2h:.2f} GB/s")
except: print("Error memoria")

print("\n--- Potencia de Cómputo ---")
for size in [4096, 8192]:
    tf32 = benchmark_compute(size, torch.float32)
    print(f"Matriz {size}x{size}: FP32: {tf32:.2f} TFLOPS", end="")
    try:
        tf16 = benchmark_compute(size, torch.float16)
        print(f" | FP16: {tf16:.2f} TFLOPS (Boost: {tf16/tf32:.2f}x)")
    except: print("")

print("\n" + "-"*60 + "\n")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y ahora, para probarlo:&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 build
docker compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Si algo falla y necesitas recrear el contenedor, debes eliminarlo previamente con:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker rm -f rocm_strix_container
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Por último, puedes ver el estado de la GPU con:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rocm-smi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;y del sistema con:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;htop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>rocm</category>
      <category>hal</category>
      <category>amdgpu</category>
      <category>ia</category>
    </item>
    <item>
      <title>Windows: container Windows + container Linux (&amp; VI)</title>
      <dc:creator>Raül Martínez i Peris</dc:creator>
      <pubDate>Thu, 09 Oct 2025 12:50:25 +0000</pubDate>
      <link>https://dev.to/elferrer/windows-container-windows-container-linux-vi-1bjg</link>
      <guid>https://dev.to/elferrer/windows-container-windows-container-linux-vi-1bjg</guid>
      <description>&lt;h4&gt;
  
  
  Índice
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Sección primera (básico):

&lt;ul&gt;
&lt;li&gt;Docker en Windows. &lt;a href="https://dev.to/elferrer/windows-container-windows-container-linux-i-54ce"&gt;Apartado I&lt;/a&gt;: Instalar WSL2 (y Docker).&lt;/li&gt;
&lt;li&gt;Docker en Windows. &lt;a href="https://dev.to/elferrer/windows-container-windows-container-linux-ii-5bhg"&gt;Apartado II&lt;/a&gt;: Instalar Docker en Windows 11.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Sección segunda (avanzado):

&lt;ul&gt;
&lt;li&gt;Docker en Windows. &lt;a href="https://dev.to/elferrer/windows-container-windows-container-linux-iii-4np9"&gt;Apartado III&lt;/a&gt;: Confianza entre entornos.&lt;/li&gt;
&lt;li&gt;Docker en Windows. &lt;a href="https://dev.to/elferrer/windows-container-windows-container-linux-iv-51eo"&gt;Apartado IV&lt;/a&gt;: Configurar conexión entre Docker Windows y Docker WSL.&lt;/li&gt;
&lt;li&gt;Docker en Windows. &lt;a href="https://dev.to/elferrer/windows-container-windows-container-linux-v-2bcn"&gt;Apartado V&lt;/a&gt;: Instalar Portainer en el entorno WSL.&lt;/li&gt;
&lt;li&gt;Docker en Windows. &lt;a href="https://dev.to/elferrer/windows-container-windows-container-linux-vi-1bjg"&gt;Apartado VI&lt;/a&gt;: Apuntes.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  Apartado VI. Apuntes
&lt;/h2&gt;

&lt;p&gt;En este momento ya tenemos los dos contextos configurados. Veamos como consultar los &lt;code&gt;context&lt;/code&gt; desde cada entorno.&lt;/p&gt;

&lt;h3&gt;
  
  
  Desde Windows
&lt;/h3&gt;

&lt;p&gt;Para ver el listado de contextos:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Para utilizar docker con Windows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker context use windows
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para utilizar docker con Ubuntu:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker context use wsl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Desde WSL
&lt;/h3&gt;

&lt;p&gt;Para ver el listado de contextos:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Para utilizar docker con Windows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker context use windows-from-wsl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para utilizar docker con Ubuntu:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker context use default
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Notas finales
&lt;/h3&gt;

&lt;p&gt;Ten en cuenta que necesitas la virtualización de WSL para poder ejecutar contenedores en el entorno Linux; por ello, hemos activado que se levante automáticamente. Aunque si lo prefieres, puedes quitar el archivo de inicio automático y simplemente levantarlo con “wsl -d Ubuntu” antes de lanzar cualquier docker del entorno Linux.&lt;/p&gt;

&lt;h3&gt;
  
  
  Errores conocidos
&lt;/h3&gt;

&lt;p&gt;Después de una actualización del sistema o por cambios en directivas, WSL no funciona correctamente: revisa si la ejecución de scripts se ha deshabilitado. Ejecuta el comando:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Get-ExecutionPolicy -List
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Si no tienes permisos para ejecutar scripts, vuelve a definirlos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Set-ExecutionPolicy Unrestricted -Scope CurrentUser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>windows</category>
      <category>docker</category>
      <category>wsl2</category>
    </item>
    <item>
      <title>Windows: container Windows + container Linux (V)</title>
      <dc:creator>Raül Martínez i Peris</dc:creator>
      <pubDate>Thu, 09 Oct 2025 12:49:50 +0000</pubDate>
      <link>https://dev.to/elferrer/windows-container-windows-container-linux-v-2bcn</link>
      <guid>https://dev.to/elferrer/windows-container-windows-container-linux-v-2bcn</guid>
      <description>&lt;h4&gt;
  
  
  Índice
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Sección primera (básico):

&lt;ul&gt;
&lt;li&gt;Docker en Windows. &lt;a href="https://dev.to/elferrer/windows-container-windows-container-linux-i-54ce"&gt;Apartado I&lt;/a&gt;: Instalar WSL2 (y Docker).&lt;/li&gt;
&lt;li&gt;Docker en Windows. &lt;a href="https://dev.to/elferrer/windows-container-windows-container-linux-ii-5bhg"&gt;Apartado II&lt;/a&gt;: Instalar Docker en Windows 11.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Sección segunda (avanzado):

&lt;ul&gt;
&lt;li&gt;Docker en Windows. &lt;a href="https://dev.to/elferrer/windows-container-windows-container-linux-iii-4np9"&gt;Apartado III&lt;/a&gt;: Confianza entre entornos.&lt;/li&gt;
&lt;li&gt;Docker en Windows. &lt;a href="https://dev.to/elferrer/windows-container-windows-container-linux-iv-51eo"&gt;Apartado IV&lt;/a&gt;: Configurar conexión entre Docker Windows y Docker WSL.&lt;/li&gt;
&lt;li&gt;Docker en Windows. &lt;a href="https://dev.to/elferrer/windows-container-windows-container-linux-v-2bcn"&gt;Apartado V&lt;/a&gt;: Instalar Portainer en el entorno WSL.&lt;/li&gt;
&lt;li&gt;Docker en Windows. &lt;a href="https://dev.to/elferrer/windows-container-windows-container-linux-vi-1bjg"&gt;Apartado VI&lt;/a&gt;: Apuntes.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;p&gt;Este apartado lo añadimos como ejemplo de instalación de un contenedor Linux que puede "manejar" también los contenedores Windows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Apartado V. Instalar Portainer en el entorno WSL
&lt;/h2&gt;

&lt;p&gt;Si no estabas dentro de la consola de Linux, entra en la distro con:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wsl -d ubuntu
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;confirma que estás en el contexto ‘default’; y sin más dilación, lancemos la creación del contenedor:&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 -d -p 9000:9000 --name portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock portainer/portainer-ce
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Una vez terminado, accedemos a la url “localhost:9000”: creamos el usuario admin, y ya está.&lt;/p&gt;

&lt;p&gt;Ahora toca configurar los dos “contextos” o “entornos”, para ello, pulsa sobre “Add Environments” y lanzamos el “Wizard” de “Docker Standalone”:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Para el Docker de WSL (Ubuntu) le indicaremos que coja el socket por defecto, le indicamos un nombre, le damos a “Connect” y después a “Close”.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Volvemos al menú para añadir otro entorno, seleccionamos Docker Standalone y “API”:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Le indicamos un nombre al entorno y le indicamos la URL. En nuestro caso la URL corresponde con el hostname &lt;code&gt;wsl2docker.local&lt;/code&gt; que hemos preparado y el puerto &lt;code&gt;2375&lt;/code&gt;. Recuerda que ya deberías haber configurado el archivo “hosts” de Windows. Además, tendrás que indicar los certificados: &lt;code&gt;ca.pem&lt;/code&gt;, &lt;code&gt;client-cert.pem&lt;/code&gt; y &lt;code&gt;client-key.pem&lt;/code&gt; que preparamos y que tienes en tu carpeta &lt;code&gt;C:\ProgramData\docker\certs&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Una vez completado pulsa el botón “Connect” y “Close”.&lt;/p&gt;

&lt;p&gt;Ahora ya podemos ir a “Home” y veremos los dos entornos. Tenemos a la vista tanto los contenedores del context/environment de Windows como de los de WSL (Linux).&lt;/p&gt;

&lt;h2&gt;
  
  
  Enlaces
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Portainer: &lt;a href="https://docs.portainer.io/start/install-ce" rel="noopener noreferrer"&gt;https://docs.portainer.io/start/install-ce&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Siguiente artículo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/elferrer/windows-container-windows-container-linux-vi-1bjg"&gt;Docker en Windows. Apartado VI&lt;/a&gt;: Apuntes.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>windows</category>
      <category>docker</category>
      <category>wsl2</category>
    </item>
    <item>
      <title>Windows: container Windows + container Linux (IV)</title>
      <dc:creator>Raül Martínez i Peris</dc:creator>
      <pubDate>Thu, 09 Oct 2025 12:49:17 +0000</pubDate>
      <link>https://dev.to/elferrer/windows-container-windows-container-linux-iv-51eo</link>
      <guid>https://dev.to/elferrer/windows-container-windows-container-linux-iv-51eo</guid>
      <description>&lt;h4&gt;
  
  
  Índice
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Sección primera (básico):

&lt;ul&gt;
&lt;li&gt;Docker en Windows. &lt;a href="https://dev.to/elferrer/windows-container-windows-container-linux-i-54ce"&gt;Apartado I&lt;/a&gt;: Instalar WSL2 (y Docker).&lt;/li&gt;
&lt;li&gt;Docker en Windows. &lt;a href="https://dev.to/elferrer/windows-container-windows-container-linux-ii-5bhg"&gt;Apartado II&lt;/a&gt;: Instalar Docker en Windows 11.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Sección segunda (avanzado):

&lt;ul&gt;
&lt;li&gt;Docker en Windows. &lt;a href="https://dev.to/elferrer/windows-container-windows-container-linux-iii-4np9"&gt;Apartado III&lt;/a&gt;: Confianza entre entornos.&lt;/li&gt;
&lt;li&gt;Docker en Windows. &lt;a href="https://dev.to/elferrer/windows-container-windows-container-linux-iv-51eo"&gt;Apartado IV&lt;/a&gt;: Configurar conexión entre Docker Windows y Docker WSL.&lt;/li&gt;
&lt;li&gt;Docker en Windows. &lt;a href="https://dev.to/elferrer/windows-container-windows-container-linux-v-2bcn"&gt;Apartado V&lt;/a&gt;: Instalar Portainer en el entorno WSL.&lt;/li&gt;
&lt;li&gt;Docker en Windows. &lt;a href="https://dev.to/elferrer/windows-container-windows-container-linux-vi-1bjg"&gt;Apartado VI&lt;/a&gt;: Apuntes.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  Apartado IV. Configurar conexión entre Docker Windows y Docker WSL
&lt;/h2&gt;

&lt;p&gt;Para empezar, abre una consola Powershell.&lt;/p&gt;

&lt;h3&gt;
  
  
  Crear reglas para el Firewall
&lt;/h3&gt;

&lt;p&gt;Como sabes, los servicios en contenedores necesitan salir por un puerto para conversar con otros servicios o aplicaciones. Por ello, es necesario que pongas una regla en el cortafuegos para permitirlo.&lt;/p&gt;

&lt;p&gt;Vamos a crear una regla de ejemplo para permitir el acceso al puerto 8080:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;New-NetFirewallRule -DisplayName "Web TCP 8080" -Direction Inbound -Protocol TCP -LocalPort 8080 -Action Allow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adicionalmente, revisa  el puerto 443 (aunque suele estar abierto) y ábrelo si es necesario:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;New-NetFirewallRule -DisplayName "HTTPS" -Direction Inbound -Protocol TCP -LocalPort 443 -Action Allow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cada vez que crees un contenedor deberás verificar que el puerto esté abierto.&lt;/p&gt;

&lt;h4&gt;
  
  
  Crear reglas en el Firewall para Docker
&lt;/h4&gt;

&lt;p&gt;Vamos a crear las reglas necesarias para la escucha entre los Docker de ambos entornos, para ello abriremos el puerto 2375.&lt;/p&gt;

&lt;p&gt;Para permitir el acceso al puerto 2375, crea la regla:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;New-NetFirewallRule -DisplayName "Docker TCP 2375" -Direction Inbound -Protocol TCP -Action Allow -LocalPort 2375
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Reiniciar el servicio Docker
&lt;/h4&gt;

&lt;p&gt;Para que los cambios surtan efecto deberemos reiniciar el servicio de Docker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;net stop Docker
net start Docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Comprueba que el servicio Docker está funcionando:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configurar el demonio de Docker WSL
&lt;/h3&gt;

&lt;p&gt;Entra en el entorno de Ubuntu:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wsl -d ubuntu
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Comprueba que tienes los archivos &lt;code&gt;wsl-server-cert.pem&lt;/code&gt;, &lt;code&gt;wsl-server-key.pem&lt;/code&gt; y &lt;code&gt;ca.pem&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ls /mnt/c/ProgramData/docker/certs/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creamos el directorio para Docker (si no lo estuviera) y creamos el archivo &lt;code&gt;daemon.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo mkdir -p /etc/docker
sudo nano /etc/docker/daemon.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;con el contenido:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
"hosts": [
  "unix:///var/run/docker.sock",
  "tcp://0.0.0.0:2376"
],
"tlsverify": true,
"tlscacert": "/mnt/c/ProgramData/certs/ca.pem",
"tlscert": "/mnt/c/ProgramData/docker/certs/wsl-server-cert.pem",
"tlskey": "/mnt/c/ProgramData/docker/certs/wsl-server-key.pem"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ahora vamos a crear el archivo override de la configuración:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo mkdir -p /etc/systemd/system/docker.service.d
sudo nano /etc/systemd/system/docker.service.d/override.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;con el contenido:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Service]
ExecStart=
ExecStart=/usr/bin/dockerd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y reiniciamos el servicio:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo systemctl daemon-reload
sudo service docker restart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El siguiente paso es con Powershell, por lo que debemos salir de Ubuntu:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;exit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y hacerle un shutdown:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wsl --shutdown
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configurar en Windows la conexión al host de Linux
&lt;/h3&gt;

&lt;p&gt;En la consola de Powershell, crea el nuevo contexto para WSL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$userProfile = $env:USERPROFILE
$dockerDir = Join-Path $userProfile ".docker"
docker context create wsl --docker "host=tcp://localhost:2376,ca=$userProfile\.docker\ca.pem,cert=$userProfile\.docker\cert.pem,key=$userProfile\.docker\key.pem"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Aunque es el contexto de WSL, lo estamos configurando en Windows por lo que las rutas para los certificados que utilizamos son los de Windows, no siendo necesario volver a copiarlos.&lt;/p&gt;

&lt;h3&gt;
  
  
  Añadir las reglas al Firewall
&lt;/h3&gt;

&lt;p&gt;Para permitir el acceso al puerto 2376, crea la regla:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;New-NetFirewallRule -DisplayName "Docker TCP 2376" -Direction Inbound -Protocol TCP -Action Allow -LocalPort 2376
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configuración del Docker en WSL con TLS
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Copiar las claves TLS a la carpeta de usuario
&lt;/h4&gt;

&lt;p&gt;Ahora vamos a configurar en WSL la conexión al host docker de Windows.&lt;/p&gt;

&lt;p&gt;Volvemos a la consola de WSL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wsl -d ubuntu
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Primero copiamos los certificados a la carpeta docker del usuario:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cp /mnt/c/ProgramData/certs/ca.pem ~/.docker/ca.pem
cp /mnt/c/ProgramData/docker/certs/client-cert.pem ~/.docker/cert.pem
cp /mnt/c/ProgramData/docker/certs/client-key.pem ~/.docker/key.pem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y les ponemos los permisos adecuados:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd ~/.docker/
chmod -v 0400 key.pem
chmod -v 0444 cert.pem ca.pem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Crear contextos dentro del entorno WSL
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Preparar el hostname de conexión
&lt;/h4&gt;

&lt;p&gt;Lo primero es automatizar el nombre hostname en cada inicio de sesión. Para ello vamos a editar el archivo de inicio de sesión:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nano ~/.bashrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;y le añadimos al final del todo el siguiente código:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WINDOWS_DOCKER_HOSTNAME="wsl2docker.local"
WINDOWS_HOST_IP=$(ip route show | grep -i default | awk '{ print $3 }')
sudo sed -i "/${WINDOWS_DOCKER_HOSTNAME}/d" /etc/hosts
echo "${WINDOWS_HOST_IP} ${WINDOWS_DOCKER_HOSTNAME}" | sudo tee -a /etc/hosts
sudo systemctl restart systemd-resolved
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;En Windows deberíamos controlar que el archivo &lt;code&gt;C:\Windows\System32\drivers\etc\hosts&lt;/code&gt; está correctamente configurado, sin embargo, crear un script Powershell que se lance automáticamente en el inicio de Windows para comprobar dicha línea “se sale” de las necesidades que tenemos.&lt;/p&gt;

&lt;p&gt;Pero, sí que necesitamos saber si la IP nos ha cambiado, para ello, añadiremos al final del archivo, a continuación de donde acabas de pegar lo anterior, en el archivo &lt;code&gt;~/.bashrc&lt;/code&gt;, una pequeña comprobación:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WINDOWS_HOSTS_PATH="/mnt/c/Windows/System32/drivers/etc/hosts"
HOSTS_LINE=$(grep -i "$WINDOWS_DOCKER_HOSTNAME" "$WINDOWS_HOSTS_PATH" | grep -v "^#")
if [[ "$HOSTS_LINE" == *"$WINDOWS_HOST_IP"* ]]; then
  echo -e "\nLa IP y el Hostname para la conexión entre WSL y Windows están correctamente configuradas en ambos entornos.\n”
else
  echo -e "\nEl archivo de configuración de Windows no está correctamente configurado,"
  echo -e "por favor, abre el archivo '$WINDOWS_HOSTS_PATH' y revisa la siguiente línea:"
  echo -e "$WINDOWS_HOST_IP  $WINDOWS_DOCKER_HOSTNAME"
fi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esta comprobación nos avisará cuando levantemos WSL2 si coinciden las configuraciones de Windows y WSL2. Si no coinciden nos dirá la ruta del archivo y el contenido que debemos poner.&lt;/p&gt;

&lt;h4&gt;
  
  
  Crear el contexto de conexión a Windows
&lt;/h4&gt;

&lt;p&gt;Ahora creamos el contexto para ver el Docker de Windows desde WSL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WINDOWS_DOCKER_HOSTNAME="wsl2docker.local"
docker context create windows-from-wsl --docker "host=tcp://${WINDOWS_DOCKER_HOSTNAME}:2375,ca=${HOME}/.docker/ca.pem,cert=${HOME}/.docker/cert.pem,key=${HOME}/.docker/key.pem"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para simplificar la comprensión de esta creación de contexto hemos creado una variable llamada hostname para que quede más claro... evidentemente esto no es necesario, puedes escribirlo directamente.&lt;/p&gt;

&lt;p&gt;Con este contexto creado, vamos a comprobarlo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker context use windows-from-wsl
docker ps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deberías de haber obtenido como resultado el listado del docker de Windows.&lt;/p&gt;

&lt;p&gt;Una vez comprobado el Docker de Windows, puedes volver al contexto predeterminado de WSL (que generalmente apunta al socket &lt;code&gt;unix:///var/run/docker.sock&lt;/code&gt;) ejecutando:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker context use default
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Salimos de la consola WSL para continuar en Powershell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;exit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Efectos secundarios
&lt;/h3&gt;

&lt;p&gt;Al introducir en el &lt;code&gt;.bashrc&lt;/code&gt; la creación del `hostname+ , tenemos como “inconveniente” que a partir de ahora cada vez que entremos en el entorno WSL nos pedirá la contraseña, ya que hemos incluido una línea de código con “sudo”, lo cual obliga a ejecutarse con los permisos de root.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;¿Porqué añadimos la IP que nos entrega &lt;code&gt;ip route show&lt;/code&gt;?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Si miras la configuración de redes de tu equipo, verás que tienes una red virtual que se llama “WSL (Hyper-V firewall)”, donde está configurada la IP de nuestro host con acceso a través del firewall. Si desde WSL compruebas con telnet verás que nos permite la conexión.&lt;/p&gt;

&lt;h3&gt;
  
  
  Finalizar la instalación
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Activar WSL en el inicio de sesión.
&lt;/h4&gt;

&lt;p&gt;El único inconveniente que tiene utilizar Docker con WSL es que necesitas tener WSL funcionando.&lt;br&gt;
Por ello, lo mejor es levantar la distro al inicio de sesión y dejar la ventana minimizada.&lt;/p&gt;

&lt;p&gt;Para añadir el arranque de WSL al inicio, pulsa &lt;code&gt;Win+R&lt;/code&gt; y escribe &lt;code&gt;shell:startup&lt;/code&gt;, y presiona Enter.&lt;/p&gt;

&lt;p&gt;Ahora, en la ventana que te ha abierto, con el ratón, crea un nuevo acceso directo y escribe:&lt;br&gt;
&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
wsl -d Ubuntu&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Para finalizar pulsa siguiente y ponle nombre al acceso directo, por ejemplo, "wsl-Ubuntu".&lt;/p&gt;

&lt;h2&gt;
  
  
  Enlaces
&lt;/h2&gt;

&lt;p&gt;Siguiente artículo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/elferrer/windows-container-windows-container-linux-v-2bcn"&gt;Docker en Windows. Apartado V&lt;/a&gt;: Instalar Portainer en el entorno WSL2.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>windows</category>
      <category>docker</category>
      <category>wsl2</category>
    </item>
    <item>
      <title>Windows: container Windows + container Linux (III)</title>
      <dc:creator>Raül Martínez i Peris</dc:creator>
      <pubDate>Thu, 09 Oct 2025 12:48:38 +0000</pubDate>
      <link>https://dev.to/elferrer/windows-container-windows-container-linux-iii-4np9</link>
      <guid>https://dev.to/elferrer/windows-container-windows-container-linux-iii-4np9</guid>
      <description>&lt;h4&gt;
  
  
  Índice
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Sección primera (básico):

&lt;ul&gt;
&lt;li&gt;Docker en Windows. &lt;a href="https://dev.to/elferrer/windows-container-windows-container-linux-i-54ce"&gt;Apartado I&lt;/a&gt;: Instalar WSL2 (y Docker).&lt;/li&gt;
&lt;li&gt;Docker en Windows. &lt;a href="https://dev.to/elferrer/windows-container-windows-container-linux-ii-5bhg"&gt;Apartado II&lt;/a&gt;: Instalar Docker en Windows 11.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Sección segunda (avanzado):

&lt;ul&gt;
&lt;li&gt;Docker en Windows. &lt;a href="https://dev.to/elferrer/windows-container-windows-container-linux-iii-4np9"&gt;Apartado III&lt;/a&gt;: Confianza entre entornos.&lt;/li&gt;
&lt;li&gt;Docker en Windows. &lt;a href="https://dev.to/elferrer/windows-container-windows-container-linux-iv-51eo"&gt;Apartado IV&lt;/a&gt;: Configurar conexión entre Docker Windows y Docker WSL.&lt;/li&gt;
&lt;li&gt;Docker en Windows. &lt;a href="https://dev.to/elferrer/windows-container-windows-container-linux-v-2bcn"&gt;Apartado V&lt;/a&gt;: Instalar Portainer en el entorno WSL.&lt;/li&gt;
&lt;li&gt;Docker en Windows. &lt;a href="https://dev.to/elferrer/windows-container-windows-container-linux-vi-1bjg"&gt;Apartado VI&lt;/a&gt;: Apuntes.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;p&gt;Con este artículo empezamos la sección avanzada. Además, este apartado lo dividiremos en dos partes: la primera para la creación un certificado CA; y, la segunda para la creación de los certificados TLS que necesitaremos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Apartado III. Confianza entre entornos
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;¿Para qué necesitamos tener un certificado de una CA?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Vamos a conectar el docker de Windows con el docker de WSL, para lo cual al tener que realizar la conexión por dominio/IP necesitamos que ambas virtualizaciones confíen entre ellas... lo que no s obliga a tener certificados firmados por una CA. Aquí tenemos dos opciones: pagamos o no pagamos. Para esta sección de Docker nos interesa el aprendizaje de crear la CA y utilizarla.&lt;/p&gt;




&lt;h3&gt;
  
  
  Primera parte: Crear una CA (Autoridad Certificadora) para usos internos
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Descargar e instalar OpenSSL
&lt;/h4&gt;

&lt;p&gt;Descarga el instalador de OpenSSL &lt;a href="https://slproweb.com/products/Win32OpenSSL.html" rel="noopener noreferrer"&gt;Win32OpenSSL de slproweb&lt;/a&gt;. Este instalador es un empaquetado por un tercero, si necesitas el código de OpenSSL sin empaquetar tienes los enlaces al final del artículo. Sea cual sea la opción que tomes, debes instalarlo antes de continuar.&lt;/p&gt;

&lt;h4&gt;
  
  
  Configurar OpenSSL
&lt;/h4&gt;

&lt;p&gt;Abre una consola Powershell.&lt;/p&gt;

&lt;p&gt;Lo primero que haremos es añadir la ruta del ejecutable al path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Environment]::SetEnvironmentVariable("PATH", "$env:PATH;C:\Program Files\OpenSSL-Win64\bin", [EnvironmentVariableTarget]::Machine)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Asegúrate de haber realizado la instalación en la ruta indicada, o, en caso de que difiera actualiza los datos de la variable de entorno.&lt;/p&gt;

&lt;p&gt;Una vez hecho esto debemos reiniciar la consola para que surta efecto.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ubicación sugerida para los certificados
&lt;/h3&gt;

&lt;p&gt;Normalmente la instalación de Docker crea todas las carpetas necesarias, pero, si no existiera lo correcto es crear la siguiente carpeta &lt;code&gt;C:\ProgramData\certs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;New-Item -ItemType Directory -Force -Path "C:\ProgramData\certs"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Generar los certificados utilizando OpenSSL
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Antes de continuar: Recuerda que debes poner especial cuidado en la salvaguarda de las contraseñas, ficheros llave y certificados.&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Generar la Llave y el Certificado de la CA (Autoridad Certificadora)
&lt;/h4&gt;

&lt;p&gt;Para que un certificado actúe como CA, debe tener la extensión &lt;code&gt;Basic Constraints&lt;/code&gt; con &lt;code&gt;CA:TRUE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Nos situamos en el directorio:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd "C:\ProgramData\certs"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Crea un archivo llamado &lt;code&gt;ca.cnf&lt;/code&gt; con el siguiente contenido:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ req ]
distinguished_name = req_distinguished_name
x509_extensions = v3_ca
prompt = no
[ req_distinguished_name ]
C = ES
ST = Comunitat Valenciana
L = Valencia
O = EMPRESA
OU = UNIDAD EMPRESARIAL
CN = Mi CA para Docker
[ v3_ca ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:TRUE
keyUsage = critical, digitalSignature, cRLSign, keyCertSign
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ajusta los campos C (Country), ST (State or Province), L (Locality), O (Organization), OU (Organizational Unit) y CN (Common Name) según tus necesidades.&lt;/p&gt;

&lt;p&gt;Es importante utilizar un CN que nos permita tener una buena descripción de su cometido, por ejemplo un CN conveniente para este caso sería &lt;code&gt;ca-$($env:COMPUTERNAME)-$($env:USERNAME)&lt;/code&gt;. Al final del artículo hemos añadido información relevante para su correcta definición.&lt;/p&gt;

&lt;p&gt;Genera el certificado llave:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;openssl genrsa -aes256 -out ca-key.pem 4096
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Te pedirá una contraseña para la clave de la CA. Esta contraseña es necesaria para firmar otros certificados. Como es habitual, guárdatela en tu &lt;em&gt;Keepass&lt;/em&gt; o similar.&lt;/p&gt;

&lt;p&gt;Genera el certificado (te pedirá la contraseña que has introducido en la llave):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;openssl req -new -x509 -days 3650 -key ca-key.pem -sha256 -out ca.pem -config ca.cnf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ahora para confirmar que es correcto, lanza el siguiente comando:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;openssl x509 -in ca.pem -text -noout | Select-String "CA"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;En el resultado tendrás que ver que &lt;code&gt;basicConstraints&lt;/code&gt; está como &lt;code&gt;critical&lt;/code&gt; y con &lt;code&gt;CA:TRUE&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configurar los permisos de los archivos para WSL
&lt;/h3&gt;

&lt;p&gt;Para ello entramos en una terminal Powershell en modo "Administrador" e introducimos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wsl -d ubuntu
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ahora nos situamos en el directorio &lt;code&gt;certs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd /mnt/c/ProgramData/certs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ahora limpiamos los archivos sobrantes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rm *.csr *.cnf *.srl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Por último, vamos a cambiar los permisos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chmod -v 0400 ca-key.pem
chmod -v 0444 ca.pem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;¿Porqué hemos cambiado los permisos desde la terminal de Ubuntu?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Por dos razones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Es más simple que con Powershell.&lt;/li&gt;
&lt;li&gt;Nos aseguramos que tenemos acceso a los archivos en el disco &lt;code&gt;C:&lt;/code&gt; de Windows desde el entorno WSL.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Evidentemente, puedes optar por cambiar los permisos desde Powershell.&lt;/p&gt;




&lt;h3&gt;
  
  
  Segunda parte: Configuración de la comunicación interna de los entornos
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Debes tener un certificado CA antes de continuar.&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Generar certificados TLS
&lt;/h4&gt;

&lt;p&gt;Antes de configurar Docker tanto en Windows como WSL necesitas generar los certificados TLS. Estos certificados asegurarán la comunicación entre el cliente Docker y el daemon Docker. Necesitarás una Autoridad Certificadora (CA), el cual ya lo habrás preparado anteriormente.&lt;/p&gt;

&lt;p&gt;Normalmente la instalación de Docker crea todas las carpetas necesarias, pero, si no existiera lo correcto es crear la carpeta &lt;code&gt;C:\ProgramData\docker\certs&lt;/code&gt; para los certificados:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;New-Item -ItemType Directory -Force -Path "C:\ProgramData\docker\certs"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nos situamos en el directorio:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd "C:\ProgramData\docker\certs"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Generar los certificados utilizando OpenSSL
&lt;/h4&gt;

&lt;p&gt;Es necesario que tengas un certificado de una CA para realizar los siguientes pasos como se indicó en el anterior apartado.&lt;/p&gt;

&lt;p&gt;Si ha seguido las indicaciones del documento, tendrás en la carpeta &lt;code&gt;C:\ProgramData\certs&lt;/code&gt; el certificado CA ya creado. Créate el enlace en una variable para acceder rápidamente a ellos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$caPem = "C:\ProgramData\certs\ca.pem"
$caKeyPem = "C:\ProgramData\certs\ca-key.pem"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Generar Clave y Certificado del Servidor para Docker en Windows
&lt;/h4&gt;

&lt;p&gt;En este paso tendremos en cuenta que debemos configurarlo para que escuche a través de localhost.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;openssl genrsa -out win-server-key.pem 4096
openssl req -subj "/CN=localhost" -sha256 -new -key win-server-key.pem -out win-server.csr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Consultamos el nombre de nuestro equipo lanzando el comando:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hostname
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ahora creamos el archivo extfile-win.cnf con el siguiente contenido utilizando el resultado de &lt;code&gt;hostname&lt;/code&gt; (sustituyendo &lt;code&gt;xNAMEx&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;subjectAltName = DNS:localhost,IP:127.0.0.1,IP:::1,DNS:xNAMEx,DNS:wsl2docker.local
extendedKeyUsage = serverAuth
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Como es lógico, cambiamos el DNS indicado (&lt;em&gt;xNAMEx&lt;/em&gt;) por el nombre de tu máquina, y, además introducimos un nuevo DNS que será utilizado para “conectar” el entorno Windows y WSL sin necesidad de depender de una IP (esto nos permite que el certificado continúe siendo válido incluso si nos cambia la IP). &lt;/p&gt;

&lt;p&gt;Ahora firma la CSR del servidor de Windows con tu CA:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$caPem = "C:\ProgramData\certs\ca.pem"
$caKeyPem = "C:\ProgramData\certs\ca-key.pem"
Copy-Item -Path $caPem "C:\ProgramData\docker\certs\ca.pem"
openssl x509 -req -days 3650 -sha256 -in win-server.csr -CA ca.pem -CAkey $caKeyPem -CAcreateserial -out win-server-cert.pem -extfile extfile-win.cnf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(te pedirá la contraseña de tu llave).&lt;/p&gt;

&lt;h4&gt;
  
  
  Generar Clave y Certificado del Servidor para Docker en WSL2
&lt;/h4&gt;

&lt;p&gt;Seguimos los mismos pasos para el de WSL2:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;openssl genrsa -out wsl-server-key.pem 4096
openssl req -subj "/CN=localhost" -sha256 -new -key wsl-server-key.pem -out wsl-server.csr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creamos el archivo configuración de extensiones extfile-wsl.cnf:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;subjectAltName = DNS:localhost,IP:127.0.0.1,IP:::1,DNS:wsl2docker.local
extendedKeyUsage = serverAuth
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Firmamos el CSR del servidor de WSL2 con el CA:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;openssl x509 -req -days 3650 -sha256 -in wsl-server.csr -CA ca.pem -CAkey $caKeyPem -CAcreateserial -out wsl-server-cert.pem -extfile extfile-wsl.cnf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(te pedirá la contraseña de tu llave).&lt;/p&gt;

&lt;h4&gt;
  
  
  Generar Clave y Certificado del Cliente (para docker CLI)
&lt;/h4&gt;

&lt;p&gt;Los mismos pasos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;openssl genrsa -out client-key.pem 4096
openssl req -subj "/CN=client" -new -key client-key.pem -out client.csr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creamos el archivo de configuración de extensiones extfile-client.cnf:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;extendedKeyUsage = clientAuth
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y firmamos el CSR:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;openssl x509 -req -days 3650 -sha256 -in client.csr -CA ca.pem -CAkey $caKeyPem -CAcreateserial -out client-cert.pem -extfile extfile-client.cnf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(te pedirá la contraseña de tu llave).&lt;/p&gt;

&lt;h4&gt;
  
  
  Configurar los permisos de los archivos
&lt;/h4&gt;

&lt;p&gt;Aprovechamos que tenemos instalado WSL y terminamos de configurar los archivos desde la terminal de Ubuntu. Para ello entramos en una terminal powershell en modo "Administrador" e introducimos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wsl -d ubuntu
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ahora nos situamos en el directorio apropiado (&lt;em&gt;certs&lt;/em&gt; de Docker):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd /mnt/c/ProgramData/docker/certs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Limpiamos los archivos sobrantes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rm *.csr *.cnf *.srl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cambiamos los permisos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chmod -v 0400 client-key.pem
chmod -v 0444 client-cert.pem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Salimos de la &lt;em&gt;shell&lt;/em&gt; de Ubuntu:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;exit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y cerramos la consola con permisos &lt;em&gt;Administrador&lt;/em&gt; que hemos abierto.&lt;/p&gt;

&lt;h4&gt;
  
  
  Configurar Docker en Windows para utilizar los certificados TLS
&lt;/h4&gt;

&lt;p&gt;Recordemos lo que tenemos. Los archivos resultantes se dividen en:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clave CA o certificadora: archivos &lt;code&gt;ca.pem&lt;/code&gt; y &lt;code&gt;ca-key.pem&lt;/code&gt;. Estos archivos debes guardarlos offline en un lugar seguro.&lt;/li&gt;
&lt;li&gt;Claves para el cliente docker: &lt;code&gt;ca.pem&lt;/code&gt;, &lt;code&gt;client-cert.pem&lt;/code&gt; y &lt;code&gt;client-key.pem&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;En cuanto a las claves para el cliente, terminarán siendo colocadas en otros directorios (pero esto lo veremos después): en WSL, en la carpeta &lt;code&gt;~/.docker/&lt;/code&gt; y en windows en la carpeta &lt;code&gt;%USERPROFILE%\.docker\&lt;/code&gt;. Vamos a ello.&lt;/p&gt;

&lt;h4&gt;
  
  
  Copiar las claves TLS a la carpeta de usuario
&lt;/h4&gt;

&lt;p&gt;Primero, nos aseguramos de que los certificados de cliente (&lt;code&gt;ca.pem&lt;/code&gt;, &lt;code&gt;client-cert.pem&lt;/code&gt;, &lt;code&gt;client-key.pem&lt;/code&gt;) están en &lt;code&gt;%USERPROFILE%\.docker\&lt;/code&gt; con el nombre adecuado. En la terminal de Powershell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$userProfile = $env:USERPROFILE
$dockerDir = Join-Path $userProfile ".docker"
If (-not (Test-Path $dockerDir)) { New-Item -Path $dockerDir -ItemType Directory }
Copy-Item "$caPem" -Destination (Join-Path $dockerDir "ca.pem")
Copy-Item "C:\ProgramData\Docker\certs\client-cert.pem" -Destination (Join-Path $dockerDir "cert.pem")
Copy-Item "C:\ProgramData\Docker\certs\client-key.pem" -Destination (Join-Path $dockerDir "key.pem")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Configuración del demonio
&lt;/h4&gt;

&lt;p&gt;Utiliza Notepad++ (o el editor que prefieras) para crear el archivo:&lt;br&gt;
&lt;code&gt;C:\ProgramData\docker\config\daemon.json&lt;/code&gt;&lt;br&gt;
con el contenido:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
"hosts": ["tcp://0.0.0.0:2375", "npipe://"],
"tlsverify": true,
"tlscacert": "C:\\ProgramData\\docker\\certs\\ca.pem",
"tlscert": "C:\\ProgramData\\docker\\certs\\win-server-cert.pem",
"tlskey": "C:\\ProgramData\\docker\\certs\\win-server-key.pem"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Configurar un contexto para Windows
&lt;/h4&gt;

&lt;p&gt;Necesitaremos utilizar “contextos” de Docker, para entre otros, conectar sin problemas desde VSCode a Docker, o desde el otro entorno.&lt;/p&gt;

&lt;p&gt;Pero, primero verificamos que &lt;strong&gt;no&lt;/strong&gt; tenemos el contexto ya creado con anterioridad:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;(Nota: Si lo tienes deberías borrarlo, utiliza el comando &lt;code&gt;docker context rm NombreContexto -f&lt;/code&gt;.)&lt;/p&gt;

&lt;p&gt;Ahora, crea el contexto con el nombre “windows” para Windows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker context create windows --docker "host=tcp://localhost:2375,ca=$userProfile\.docker\ca.pem,cert=$userProfile\.docker\cert.pem,key=$userProfile\.docker\key.pem"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verifica que se ha creado correctamente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker context inspect windows
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y actívalo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker context use windows
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Apuntes
&lt;/h2&gt;

&lt;p&gt;Para crear una CA hay que tener en cuenta las siguientes normativas o estándares:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Formato y estructura. X.509 implementado vía RFC 5280: &lt;a href="https://datatracker.ietf.org/doc/html/rfc5280" rel="noopener noreferrer"&gt;https://datatracker.ietf.org/doc/html/rfc5280&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Certificados web (SSL/TLS): &lt;a href="https://cabforum.org/working-groups/server/baseline-requirements/about/" rel="noopener noreferrer"&gt;https://cabforum.org/working-groups/server/baseline-requirements/about/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Validez legal, reglamento &lt;a href="https://www.boe.es/doue/2014/257/L00073-00114.pdf" rel="noopener noreferrer"&gt;UE eIDAS&lt;/a&gt; y leyes nacionales &lt;a href="https://www.boe.es/eli/es/l/2020/11/11/6/con" rel="noopener noreferrer"&gt;BOE&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Funcionamiento interno, &lt;em&gt;Declaración de Prácticas de Certificación&lt;/em&gt; de cada CA específica, por lo que debe buscar la DPC que puede afectarle para documentarse. Como ejemplo enlazamos la normativa de la FNMT (emisión de certificados oficiales en España) en su web de la &lt;a href="https://www.sede.fnmt.gob.es/normativa" rel="noopener noreferrer"&gt;Sede Electrónica de la Real Casa de la Moneda&lt;/a&gt;; y, al BOE con la disposición para la &lt;a href="https://www.boe.es/eli/es/o/2006/03/13/int738" rel="noopener noreferrer"&gt;expedición de DNI y certificados digitales&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Enlaces
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.openssl.org/" rel="noopener noreferrer"&gt;OpenSSL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/openssl/openssl/releases" rel="noopener noreferrer"&gt;GitHub de OpenSSL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Empaquetado para Windows por &lt;a href="https://slproweb.com/products/Win32OpenSSL.html" rel="noopener noreferrer"&gt;Shining Light Productions&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Siguiente artículo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/elferrer/windows-container-windows-container-linux-iv-51eo"&gt;Docker en Windows. Apartado IV&lt;/a&gt;: Configurar conexión entre Docker Windows y Docker WSL.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>windows</category>
      <category>docker</category>
      <category>wsl2</category>
    </item>
    <item>
      <title>Windows: container Windows + container Linux (II)</title>
      <dc:creator>Raül Martínez i Peris</dc:creator>
      <pubDate>Thu, 09 Oct 2025 12:48:05 +0000</pubDate>
      <link>https://dev.to/elferrer/windows-container-windows-container-linux-ii-5bhg</link>
      <guid>https://dev.to/elferrer/windows-container-windows-container-linux-ii-5bhg</guid>
      <description>&lt;h4&gt;
  
  
  Índice
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Sección primera (básico):

&lt;ul&gt;
&lt;li&gt;Docker en Windows. &lt;a href="https://dev.to/elferrer/windows-container-windows-container-linux-i-54ce"&gt;Apartado I&lt;/a&gt;: Instalar WSL2 (y Docker).&lt;/li&gt;
&lt;li&gt;Docker en Windows. &lt;a href="https://dev.to/elferrer/windows-container-windows-container-linux-ii-5bhg"&gt;Apartado II&lt;/a&gt;: Instalar Docker en Windows 11.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Sección segunda (avanzado):

&lt;ul&gt;
&lt;li&gt;Docker en Windows. &lt;a href="https://dev.to/elferrer/windows-container-windows-container-linux-iii-4np9"&gt;Apartado III&lt;/a&gt;: Confianza entre entornos.&lt;/li&gt;
&lt;li&gt;Docker en Windows. &lt;a href="https://dev.to/elferrer/windows-container-windows-container-linux-iv-51eo"&gt;Apartado IV&lt;/a&gt;: Configurar conexión entre Docker Windows y Docker WSL.&lt;/li&gt;
&lt;li&gt;Docker en Windows. &lt;a href="https://dev.to/elferrer/windows-container-windows-container-linux-v-2bcn"&gt;Apartado V&lt;/a&gt;: Instalar Portainer en el entorno WSL.&lt;/li&gt;
&lt;li&gt;Docker en Windows. &lt;a href="https://dev.to/elferrer/windows-container-windows-container-linux-vi-1bjg"&gt;Apartado VI&lt;/a&gt;: Apuntes.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  Apartado II. Instalar Docker en Windows 11
&lt;/h2&gt;

&lt;p&gt;En este documento mostramos como instalar Docker con la licencia gratuita.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Nota:&lt;/em&gt; durante todo el proceso de instalación vas a necesitar ejecutar los comandos de Powershell en una consola con permisos de Administrador.&lt;/p&gt;

&lt;p&gt;Por defecto Windows tiene la ejecución de scripts deshabilitada (solo permite los ‘RemoteSigned’). &lt;/p&gt;

&lt;p&gt;La primera vez, para habilitar la ejecución de scripts hay que lanzar el comando:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Set-ExecutionPolicy Unrestricted -Scope CurrentUser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Activar contenedores Windows
&lt;/h3&gt;

&lt;p&gt;Instalamos las capacidades de contenedores para Windows (te pedirá reiniciar):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All
Enable-WindowsOptionalFeature -Online -FeatureName Containers -All
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reinicia el ordenador (si no lo has hecho tras instalar las features) para que se apliquen las configuraciones.&lt;/p&gt;

&lt;h3&gt;
  
  
  Instalación de Docker en Windows
&lt;/h3&gt;

&lt;p&gt;Para una instalación simple y rápida Docker en Windows tenemos un script automatizado, tienes el enlace al final del documento. Pero, mejor realicemos la instalación manual...&lt;/p&gt;

&lt;p&gt;Vamos a ello, descarga el binario de Docker CLI desde la línea de comandos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Set-Location $env:USERPROFILE\Downloads
Invoke-WebRequest -Uri "https://download.docker.com/win/static/stable/x86_64/docker-27.4.1.zip" -OutFile "docker.zip"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Extrae el archivo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Expand-Archive -Path "docker.zip" -DestinationPath “C:\ProgramData”
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agrega la CLI al path de Windows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$env:Path += ";C:\ProgramData\docker"
[Environment]::SetEnvironmentVariable("Path", $env:Path, [EnvironmentVariableTarget]::Machine)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Creación del servicio para Windows
&lt;/h4&gt;

&lt;p&gt;Crea el servicio para Windows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sc.exe create Docker binPath= "C:\ProgramData\docker\dockerd.exe --run-service" start= auto
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;e inícialo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;net start Docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Instalar ‘docker compose’
&lt;/h4&gt;

&lt;p&gt;Comprueba si está instalado:&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 version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;En caso negativo, vamos a instalar el &lt;em&gt;plug-in&lt;/em&gt; para ‘docker compose’...&lt;/p&gt;

&lt;p&gt;Primero crea la carpeta para los &lt;em&gt;plug-ins&lt;/em&gt; (si no está creada):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir C:\ProgramData\docker\cli-plugins -Force
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ahora descarga el &lt;em&gt;plug-in&lt;/em&gt; directamente en la carpeta:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Invoke-WebRequest -Uri "https://github.com/docker/compose/releases/latest/download/docker-compose-windows-x86_64.exe" -OutFile "C:\ProgramData\docker\cli-plugins\docker-compose.exe"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finalmente, comprueba que funciona:&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 version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Enlaces
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;GitHub de Microsoft: &lt;a href="https://github.com/microsoft/nav-arm-templates/blob/main/InstallOrUpdateDockerEngine.ps1" rel="noopener noreferrer"&gt;instalador de Docker&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Siguiente artículo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/elferrer/windows-container-windows-container-linux-iii-4np9"&gt;Docker en Windows. Apartado III&lt;/a&gt;: Confianza entre entornos.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>windows</category>
      <category>docker</category>
      <category>wsl2</category>
    </item>
    <item>
      <title>Windows: container Windows + container Linux (I)</title>
      <dc:creator>Raül Martínez i Peris</dc:creator>
      <pubDate>Thu, 09 Oct 2025 12:47:46 +0000</pubDate>
      <link>https://dev.to/elferrer/windows-container-windows-container-linux-i-54ce</link>
      <guid>https://dev.to/elferrer/windows-container-windows-container-linux-i-54ce</guid>
      <description>&lt;h4&gt;
  
  
  Índice
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Sección primera (básico):

&lt;ul&gt;
&lt;li&gt;Docker en Windows. &lt;a href="https://dev.to/elferrer/windows-container-windows-container-linux-i-54ce"&gt;Apartado I&lt;/a&gt;: Instalar WSL2 (y Docker).&lt;/li&gt;
&lt;li&gt;Docker en Windows. &lt;a href="https://dev.to/elferrer/windows-container-windows-container-linux-ii-5bhg"&gt;Apartado II&lt;/a&gt;: Instalar Docker en Windows 11.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Sección segunda (avanzado):

&lt;ul&gt;
&lt;li&gt;Docker en Windows. &lt;a href="https://dev.to/elferrer/windows-container-windows-container-linux-iii-4np9"&gt;Apartado III&lt;/a&gt;: Confianza entre entornos.&lt;/li&gt;
&lt;li&gt;Docker en Windows. &lt;a href="https://dev.to/elferrer/windows-container-windows-container-linux-iv-51eo"&gt;Apartado IV&lt;/a&gt;: Configurar conexión entre Docker Windows y Docker WSL.&lt;/li&gt;
&lt;li&gt;Docker en Windows. &lt;a href="https://dev.to/elferrer/windows-container-windows-container-linux-v-2bcn"&gt;Apartado V&lt;/a&gt;: Instalar Portainer en el entorno WSL.&lt;/li&gt;
&lt;li&gt;Docker en Windows. &lt;a href="https://dev.to/elferrer/windows-container-windows-container-linux-vi-1bjg"&gt;Apartado VI&lt;/a&gt;: Apuntes.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;p&gt;Si has empezado a leer es porque te has encontrado con la necesidad de poner en funcionamiento la virtualización para contenedores Windows y te has tropezado con que no podías utilizar contenedores Linux. Además, si te instalas la virtualización para contenedores Linux te pasará que no puedes utilizar la virtualización Hyper-V de Windows.&lt;/p&gt;

&lt;p&gt;En esta sección crearemos un entorno en el cual podamos lanzar contenedores Windows y Linux.&lt;/p&gt;

&lt;p&gt;Hemos dividido esta sección en dos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;En la primera (básica), hacemos una instalación de WSL2 y Docker (en Windows y WSL2).&lt;/li&gt;
&lt;li&gt;En la segunda (avanzada), el resto de los apartados, en los cuales realizamos la configuración de ambos entornos Docker y los conectamos, lo cual incluye poner en funcionamiento una CA para usos internos.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Apartado I. Instalar WSL2 (y Docker)
&lt;/h2&gt;

&lt;p&gt;Se llama WSL al Subsistema Linux en Windows, una virtualización capaz de tener funcionando un sistema Linux dentro de un sistema Windows.&lt;/p&gt;

&lt;p&gt;Pon atención a la consola que estás utilizando, ya que te encontrarás con tres casos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Consola CMD.&lt;/li&gt;
&lt;li&gt;Consola Powershell.&lt;/li&gt;
&lt;li&gt;Consola Linux.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Por defecto utilizaremos la consola Powershell.&lt;/p&gt;

&lt;h3&gt;
  
  
  Comprobar instalación de WSL2
&lt;/h3&gt;

&lt;p&gt;Primero intentaremos actualizar WSL a la versión 2. Inicia una terminal Powershell y ejecuta:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wsl --update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Si recibes un error porque no tienes habilitado el subsistema, pasa al siguiente apartado, si se ha actualizado, verifica las distribuciones instaladas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wsl --list --verbose
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Si ves que la distribución de Ubuntu está en estado Stopped, puedes ponerla en ejecución con:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wsl -d Ubuntu
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vuelve al entorno Powershell, para ello debes salir de la terminal de Ubuntu, ejecuta:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;exit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Instalar WSL2 y Ubuntu en el caso de que no esté instalado
&lt;/h4&gt;

&lt;p&gt;Si no está instalado WSL, debes habilitar la característica WSL, para ello ejecuta en la consola PowerShell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Es aconsejable que una vez terminada la instalación (puede tardar bastante) se haga un reinicio del ordenador.&lt;/p&gt;

&lt;p&gt;Después de reiniciar, habilita el hipervisor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bcdedit /set hypervisorlaunchtype auto
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;... y vuelve a reiniciar.&lt;/p&gt;

&lt;p&gt;Una vez activada la característica de virtualización WSL, pasamos a instalar una distribución de Linux (Ubuntu):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wsl --install -d Ubuntu
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esto descargará, instalará y configurará Ubuntu en el entorno WSL. Para finalizar te pedirá los datos para dar de alta un usuario y su nueva contraseña para el entorno de la distribución instalada.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuración de WSL2
&lt;/h3&gt;

&lt;p&gt;Ahora vamos a establecer WSL2 como predeterminado. Ejecuta:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wsl --set-default-version 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inicia una sesión en Ubuntu:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wsl -d Ubuntu
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Revisamos que &lt;code&gt;systemd&lt;/code&gt; está habilitado inspeccionando el archivo correspondiente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat /etc/wsl.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El comando nos devolverá el contenido del archivo, que debe ser el siguiente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[boot]
systemd=true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Si el contenido no es ese, lo editamos con:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo nano /etc/wsl.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;... y lo actualizamos.&lt;/p&gt;

&lt;p&gt;Para guardar los cambios salimos con Ctrl+X (te pedirá confirmar el guardado de datos).&lt;/p&gt;

&lt;p&gt;El paso final en la configuración de la distribución es reiniciarla, para ello primero salimos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;exit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;y, acto seguido paramos la distro. Tienes diferentes maneras de parar la máquina virtual de la distribución Ubuntu:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Una vez realizado el ‘exit’: esperar al menos 8 segundos, después de este lapso de tiempo se habrá parado la distribución.&lt;/li&gt;
&lt;li&gt;Realizar un apagado de la distribución:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wsl --terminate Ubuntu
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Realizar el shutdown del subsistema WSL:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wsl --shutdown
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esta primera vez optamos por utilizar el shutdown de WSL.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configurar el consumo de recursos de WSL
&lt;/h3&gt;

&lt;p&gt;Opcionalmente podemos decidir la configuración de la virtualización.&lt;/p&gt;

&lt;p&gt;En la &lt;strong&gt;consola CMD&lt;/strong&gt;, crea el archivo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;notepad %UserProfile%\.wslconfig
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y añádele el contenido:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[wsl2]
memory=8GB
processors=4
swap=2GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para cambiar los parámetros a tu gusto tienes un enlace a &lt;em&gt;Microsoft Learn&lt;/em&gt; al final del artículo. &lt;/p&gt;

&lt;h3&gt;
  
  
  Instalación de Docker en Ubuntu
&lt;/h3&gt;

&lt;p&gt;Para continuar abrimos una consola Powershell.&lt;/p&gt;

&lt;h4&gt;
  
  
  A) Verificar uso de &lt;em&gt;systemd&lt;/em&gt; en Ubuntu
&lt;/h4&gt;

&lt;p&gt;Accedemos a la terminal de Ubuntu:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wsl -d Ubuntu
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para verificar que el inicio está apuntando a &lt;em&gt;systemd&lt;/em&gt; lanzamos el siguiente comando:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;stat /sbin/init | grep "systemd"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;nos debería dar como resultado:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;File: /sbin/init -&amp;gt; ../lib/systemd/systemd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  B) Instalar Docker en Ubuntu
&lt;/h4&gt;

&lt;p&gt;Ahora vamos a instalar docker en la distribución Ubuntu (extraído de la web de Docker):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt update &amp;amp;&amp;amp; sudo apt upgrade -y
sudo apt install -y apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list &amp;gt; /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo usermod -aG docker $USER
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Comprobamos que se ha instalado:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;h4&gt;
  
  
  C) Configuración del firewall en Ubuntu
&lt;/h4&gt;

&lt;p&gt;El siguiente paso es configurar el firewall en la distribución Ubuntu:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo update-alternatives --config iptables
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;y seleccionamos la opción '1' que corresponde a &lt;code&gt;iptables-legacy&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enlaces
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Docker: &lt;a href="https://www.docker.com/" rel="noopener noreferrer"&gt;https://www.docker.com/&lt;/a&gt; , &lt;a href="https://docs.docker.com/engine/install/ubuntu/" rel="noopener noreferrer"&gt;https://docs.docker.com/engine/install/ubuntu/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Configuración WSL: &lt;a href="https://learn.microsoft.com/en-us/windows/wsl/wsl-config#main-wsl-settings" rel="noopener noreferrer"&gt;Advanced settings configuration in WSL | Microsoft Learn&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Siguiente artículo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/elferrer/windows-container-windows-container-linux-ii-5bhg"&gt;Docker en Windows. Apartado II&lt;/a&gt;: Instalar Docker en Windows 11.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>windows</category>
      <category>docker</category>
      <category>wsl2</category>
    </item>
    <item>
      <title>Python. Project Structure (&amp; X)</title>
      <dc:creator>Raül Martínez i Peris</dc:creator>
      <pubDate>Sun, 05 Oct 2025 16:40:58 +0000</pubDate>
      <link>https://dev.to/elferrer/python-project-structure-x-82d</link>
      <guid>https://dev.to/elferrer/python-project-structure-x-82d</guid>
      <description>&lt;h4&gt;
  
  
  Índice:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Sección A. Estructurar un proyecto: &lt;a href="https://dev.to/elferrer/python-i-4cp6"&gt;apartado I&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-estructurar-un-proyecto-ii-5d28"&gt;apartado II&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-project-structure-iii-4gib"&gt;apartado III&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-project-structure-iv-1jke"&gt;apartado IV&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-project-structure-v-h72"&gt;apartado V&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-project-structure-vi-5a00"&gt;apartado VI&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-project-structure-vii-2ggd"&gt;apartado VII&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-project-structure-viii-17ha"&gt;apartado VIII&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-project-structure-ix-3d80"&gt;apartado IX&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-project-structure-x-82d"&gt;apartado X&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;En este último apartado de la sección "Estructurar un proyecto" hablaremos de la revisión de código en grupo.&lt;/p&gt;

&lt;p&gt;La revisión de código es una parte fundamental para el trabajo en grupo en equipos de desarrollo, por ello es necesario tener claros los límites y alcances.&lt;/p&gt;

&lt;p&gt;Es imprescindible mantener unas buenas prácticas para que una revisión de código sea un factor de mejora de las interrelaciones del grupo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;El foco tiene que ser estricto: se habla del tema y solo del tema. Después habrá tiempo para otras cosas.&lt;/li&gt;
&lt;li&gt;Se revisa el código, nunca se habla de las personas que lo escribieron ni porqué; solo una revisión empática y profesional podrá ser constructiva. Todos deben entender que es un momento para entablar una conversación con la mente abierta y mejorar el trabajo en equipo para el futuro.&lt;/li&gt;
&lt;li&gt;Siempre limitamos el tiempo; cuanto más corta más eficaz, cuanto más larga más pesada.&lt;/li&gt;
&lt;li&gt;No se realizan a diario, ni semanalmente: solo cuando son necesarias.&lt;/li&gt;
&lt;li&gt;Recuerda que: si acudes a la reunión sin haber leído mínimamente el código y el objetivo de la reunión, te convertirás en un &lt;em&gt;troll&lt;/em&gt; que destrozará todo el mobiliario y amistades.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Fase 1: preparación
&lt;/h2&gt;

&lt;p&gt;En esta fase &lt;strong&gt;nunca revisamos código&lt;/strong&gt;. ¿Qué se hace?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Definir el alcance: seleccionamos un componente, archivo, módulo, que esté causando errores y que sea crítico para el negocio; o, que por urgencia haya que actuar con celeridad.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Preparar el contexto (y el código involucrado): haremos un breve resumen de qué hace el código, cual es su importancia y los problemas o dudas que se deben resolver. En este punto es habitual tener unas primera preguntas u observaciones sobre las que empezar a hablar.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Acceso al código: debemos asegurarnos que todas las personas del grupo tienen acceso y han podido revisar mínimamente el código para saber sobre qué se va a trabajar.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Fase 2: la reunión
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Deben estar limitadas a 30 ó 60 minutos como mucho. No es una reunión para escribir código, es una reunión para encontrar soluciones.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Para evitar las confusiones y desapegos es necesario mantener unas ciertas reglas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;una persona será el moderador, manteniendo el  enfoque en la reunión y asegurándose que todos participen.&lt;/li&gt;
&lt;li&gt;habrá también un presentador (en equipos muy pequeños puede ser el mismo moderador), que se encargará de mostrar el código y explicar las decisiones que llevaron a escribir dicho código; por ello es importante que preferiblemente sea quien escribió el código o alguien que esté al corriente del mismo.&lt;/li&gt;
&lt;li&gt;y los revisores, el resto de personas involucradas en el equipo. Su principal enfoque es repreguntar el código, detectar posible fallos y proponer mejoras.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;La sesión la dividiremos en tres partes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;La introducción, corta (5 minutos o poco más); en ella se explica el objetivo del código y el problema que tenemos o en qué debemos enfocarnos.&lt;/li&gt;
&lt;li&gt;Revisión del código (esta es la parte gruesa del tiempo ocupado), donde se mostrarán las secciones del código explicando su intención y lógica. &lt;strong&gt;No se lee el código línea a línea.&lt;/strong&gt; En consecuenca, surgirán posibles temas de debate:&lt;/li&gt;
&lt;li&gt;Arquitectura. ¿Se repestan los patrones de diseño (SOLID, DRY, etc.)?&lt;/li&gt;
&lt;li&gt;Mantenibilidad. ¿Qué dificultad aporta su diseño para que un nuevo desarrollador que lo desconoce pueda cambiar la lógica del código (refactor)?&lt;/li&gt;
&lt;li&gt;Escalabilidad. ¿Será igualmente eficiente cuando evolucione?&lt;/li&gt;
&lt;li&gt;Test. ¿Es posible simplificar los tests de las funcionalidades afectadas?&lt;/li&gt;
&lt;li&gt;Resultados. Una vez terminada la sesión, el moderador o la persona que se encargara habrá tomado notas y se revisarán en conjunto para clasificar las actuaciones y nombrar un responsable según sean:&lt;/li&gt;
&lt;li&gt;Actuaciones críticas: bugs, seguridad. Saldrá un grupo o persona encargada de que se solucione urgentemente.&lt;/li&gt;
&lt;li&gt;Importantes: diseño o refactorización. Se programará una tarea a futuro con un responsable.&lt;/li&gt;
&lt;li&gt;Mejoras: pequeños retoques o de estilo. Se valorará si el beneficio merece el esfuerzo y tiempo.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Fase 3: seguimiento
&lt;/h2&gt;

&lt;p&gt;El resultado de la reunión quedará por escrito, documentada con una lista de acciones y responsable utilizando la herramienta deseada.&lt;/p&gt;

&lt;p&gt;Los responsables designados se encargarán de realizar o establecer los equipos responsables de actuar.&lt;/p&gt;

&lt;p&gt;Para el cierre de los tickets/incidencias/refactor (o como se quiera categorizar) es necesario que el presentador de la reunión muestre su conformidad con el resultado obtenido.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enlaces
&lt;/h2&gt;

&lt;p&gt;Repositorio del proyecto:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/elferrer/la_fragua.git" rel="noopener noreferrer"&gt;https://gitlab.com/elferrer/la_fragua.git&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Lecturas de interés:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clean Code (Robert C. Martin).&lt;/li&gt;
&lt;li&gt;Refactoring: Improving the Design of Existing Code (Martin Fowler).&lt;/li&gt;
&lt;li&gt;Código sostenible (Carlos Blé).&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>ci</category>
      <category>cd</category>
      <category>docker</category>
    </item>
    <item>
      <title>Python. Project Structure (IX)</title>
      <dc:creator>Raül Martínez i Peris</dc:creator>
      <pubDate>Sun, 05 Oct 2025 09:17:36 +0000</pubDate>
      <link>https://dev.to/elferrer/python-project-structure-ix-3d80</link>
      <guid>https://dev.to/elferrer/python-project-structure-ix-3d80</guid>
      <description>&lt;h4&gt;
  
  
  Índice:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Sección A. Estructurar un proyecto: &lt;a href="https://dev.to/elferrer/python-i-4cp6"&gt;apartado I&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-estructurar-un-proyecto-ii-5d28"&gt;apartado II&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-project-structure-iii-4gib"&gt;apartado III&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-project-structure-iv-1jke"&gt;apartado IV&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-project-structure-v-h72"&gt;apartado V&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-project-structure-vi-5a00"&gt;apartado VI&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-project-structure-vii-2ggd"&gt;apartado VII&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-project-structure-viii-17ha"&gt;apartado VIII&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-project-structure-ix-3d80"&gt;apartado IX&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-project-structure-x-82d"&gt;apartado X&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;En este artículo daremos un breve paseo a las particularidades de los archivos involucrados en la generación de la aplicación &lt;code&gt;.exe&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Archivos involucrados
&lt;/h2&gt;

&lt;p&gt;Como hemos visto en el artículo anterior (&lt;a href="https://dev.to/elferrer/python-project-structure-viii-17ha"&gt;apartado VIII&lt;/a&gt;), hemos creado una batería de archivos para conseguir generar el &lt;code&gt;.exe&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;.gitlab-ci.yml&lt;/code&gt;, tarea &lt;code&gt;launch-build-exe&lt;/code&gt;: lanzador en el &lt;em&gt;pipeline&lt;/em&gt; de &lt;em&gt;Gitlab&lt;/em&gt; de la &lt;em&gt;Action&lt;/em&gt; de &lt;em&gt;GitHub&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ci/github-build.yml&lt;/code&gt;: &lt;em&gt;Action&lt;/em&gt; de &lt;em&gt;GitHub&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;app/main.spec&lt;/code&gt;: especificaciones para la generación del &lt;code&gt;.exe&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;extra-hooks/hook-pydantic.py&lt;/code&gt;: módulos y submódulos de pydantic.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Lanzamiento de la &lt;em&gt;Action&lt;/em&gt; de &lt;em&gt;GitHub&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;Empezamos por el principio: lo primero que ocurrirá es que el &lt;em&gt;pipeline&lt;/em&gt; de &lt;em&gt;Gitlab&lt;/em&gt; provocará el lanzamiento de la &lt;em&gt;Action&lt;/em&gt; de &lt;em&gt;GitHub&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;En la tarea &lt;code&gt;launch-build-exe&lt;/code&gt; realizamos lo siguiente:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;En &lt;em&gt;before_script&lt;/em&gt; añadimos los paquetes necesarios y copiamos los archivos &lt;code&gt;.env&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Comprimimos el repositorio (excepto los archivos innecesarios).&lt;/li&gt;
&lt;li&gt;Configuramos git.&lt;/li&gt;
&lt;li&gt;Hacemos un clonado del repositorio de &lt;em&gt;GitHub&lt;/em&gt; y borramos su contenido (con el fin posterior de actualizar su contenido).&lt;/li&gt;
&lt;li&gt;Descomprimimos el 'zip' en la carpeta del proyecto clonado.&lt;/li&gt;
&lt;li&gt;Añadimos dinámicamente el &lt;em&gt;workflow&lt;/em&gt; para &lt;em&gt;GitHub&lt;/em&gt; y limpiamos la carpeta origen (&lt;code&gt;ci&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;El último paso es hacer un &lt;em&gt;commit&lt;/em&gt; y un &lt;em&gt;push&lt;/em&gt; para actualizar el repo de &lt;em&gt;GitHub&lt;/em&gt; y desencadenar la construcción.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Como última nota a este punto cabe destacar que solo se disparará la acción si se cumplen las condiciones establecidas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ejecución de la &lt;em&gt;Action&lt;/em&gt; en &lt;em&gt;GitHub&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;En el instante en que se actualiza el repositorio  en &lt;em&gt;GitHub&lt;/em&gt; se desencadena la &lt;em&gt;Action&lt;/em&gt; que se encuentra &lt;code&gt;.github/workflows&lt;/code&gt;. En el 'github-build' podemos ver los siguientes pasos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Realizamos un &lt;code&gt;checkout&lt;/code&gt; del repositorio.&lt;/li&gt;
&lt;li&gt;Confirmamos que tenemos el archivo &lt;code&gt;.env.production&lt;/code&gt; de producción. Si no existiera se daría por finalizado la &lt;em&gt;Action&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Preparamos el entorno Python: instalación de Python, creación del entorno virtual &lt;code&gt;venv&lt;/code&gt;, actualización de &lt;code&gt;pip&lt;/code&gt;, instalar dependencias manualmente (por si alguna biblioteca no se instala automáticamente), instalar dependencias de &lt;code&gt;requirements&lt;/code&gt;, instalar &lt;code&gt;pyinstaller&lt;/code&gt; y las bibliotecas para los &lt;em&gt;hook&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Verificamos que los paquetes necesarios se han instalado.&lt;/li&gt;
&lt;li&gt;Instalamos WebView2 runtime (para pywebview) descargándolo de la URL de Microsoft.&lt;/li&gt;
&lt;li&gt;Compilamos la aplicación con PyInstaller, copiamos las DLL's necesarias y plugins de Qt6 a la carpeta &lt;code&gt;dist&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Y, como punto final, publicamos el artefacto y generamos un tag.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Generación del &lt;code&gt;.exe&lt;/code&gt; con &lt;em&gt;PyInstaller&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;En el archivo &lt;code&gt;main.spec&lt;/code&gt; tenemos las especificaciones detalladas de cómo se va a compilar la aplicación.&lt;/p&gt;

&lt;p&gt;Sin embargo, necesitamos que los módulos de nuestra aplicación estén disponibles, por lo que utilizamos un &lt;em&gt;hook&lt;/em&gt; personalizado (archivo &lt;code&gt;hook-pydantic.py&lt;/code&gt;) que se encargará de incluir explícitamente todos los submódulos de &lt;em&gt;pydantic&lt;/em&gt; y &lt;em&gt;pydantic_settings&lt;/em&gt; en la lista de importaciones ocultas. De esta forma evitamos que la compilación falle por falta de librerías.&lt;/p&gt;

&lt;p&gt;Veamos las fases según nuestro &lt;code&gt;main.spec&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Recolección y preparación de archivos
&lt;/h3&gt;

&lt;p&gt;Recogemos los &lt;em&gt;datas&lt;/em&gt; necesarios. Para ello tenemos la función &lt;code&gt;collect_all_data&lt;/code&gt; con la que recorremos recursivamente la carpeta &lt;code&gt;app&lt;/code&gt; y añadimos todos los archivos necesarios a la colección de &lt;em&gt;datas&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Añadimos las dependencias de sistema para incluirlas en el &lt;code&gt;.exe&lt;/code&gt;, buscamos y añadimos las DLL's de: &lt;code&gt;site_packages&lt;/code&gt;, &lt;code&gt;qt6_base&lt;/code&gt;, &lt;code&gt;qt_bin&lt;/code&gt; y &lt;code&gt;qt_plugins&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Añadimos los recursos de &lt;em&gt;NiceGUI&lt;/em&gt;: archivos &lt;code&gt;static&lt;/code&gt; y las &lt;code&gt;templates&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Añadimos nuestro archivo de configuración &lt;code&gt;.env.production&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Añadimos un icono si existe (&lt;code&gt;app/assets/icon.ico&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Y añadimos las importaciones ocultas con &lt;code&gt;hiddenimports&lt;/code&gt; recolectadas por el &lt;em&gt;hook&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Análisis y empaquetado
&lt;/h3&gt;

&lt;p&gt;Ahora que tenemos todos los archivos, vamos a construir el paquete siguiendo los siguientes pasos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Análisis: analizamos &lt;code&gt;app/main.py&lt;/code&gt;, los &lt;em&gt;datas&lt;/em&gt;, las importaciones ocultas, y generamos los metadatos. Puedes ver que incluimos los &lt;em&gt;PyQt6&lt;/em&gt; manualmente.&lt;/li&gt;
&lt;li&gt;Empaquetado: comprimimos el código fuente de Python y los módulos en un archivo &lt;code&gt;.pyz&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Crear el ejecutable: creamos el ejecutable, que incluye el archivo &lt;code&gt;.pyz&lt;/code&gt; del empaquetado, los &lt;em&gt;bootloaders&lt;/em&gt; de &lt;em&gt;PyInstaller&lt;/em&gt;, las propiedades (nombre, icono, compresión UPX, habilitado de la consola)&lt;/li&gt;
&lt;li&gt;Ensamblaje final: se prepara la carpeta &lt;code&gt;dist&lt;/code&gt; y se incluyen el &lt;code&gt;.exe&lt;/code&gt;, empaquetados del &lt;em&gt;source code&lt;/em&gt; así como todo lo que fuera necesario.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La parte que suele dar más quebraderos de cabeza suele ser la de los &lt;em&gt;hiddenimports&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Apuntes
&lt;/h2&gt;

&lt;p&gt;Hay que tener en cuenta que este empaquetado es muy básico y solo incluye las necesidades mínimas. Tal como vaya creciendo la aplicación habrá que ir actualizando el guión de preparación del ejecutable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enlaces
&lt;/h2&gt;

&lt;p&gt;Repositorio del proyecto:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/elferrer/la_fragua.git" rel="noopener noreferrer"&gt;https://gitlab.com/elferrer/la_fragua.git&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Enlaces de interés:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uso de &lt;em&gt;PyInstaller&lt;/em&gt;: &lt;a href="https://pyinstaller.org/en/stable/usage.html#options-group-what-to-bundle-where-to-search" rel="noopener noreferrer"&gt;https://pyinstaller.org/en/stable/usage.html#options-group-what-to-bundle-where-to-search&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Utilización de archivos &lt;code&gt;.spec&lt;/code&gt;: &lt;a href="https://pyinstaller.org/en/stable/spec-files.html" rel="noopener noreferrer"&gt;https://pyinstaller.org/en/stable/spec-files.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Sobre los &lt;em&gt;hook&lt;/em&gt;: &lt;a href="https://pyinstaller.org/en/stable/hooks.html" rel="noopener noreferrer"&gt;https://pyinstaller.org/en/stable/hooks.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Empaquetado de &lt;em&gt;NiceGUI&lt;/em&gt;: &lt;a href="https://nicegui.io/documentation/section_configuration_deployment#package_for_installation" rel="noopener noreferrer"&gt;https://nicegui.io/documentation/section_configuration_deployment#package_for_installation&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Siguiente artículo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/elferrer/python-project-structure-x-82d"&gt;Sección A. Estructurar un proyecto: apartado X&lt;/a&gt;: Revisión de código, trabajo en equipo.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>ci</category>
      <category>cd</category>
      <category>docker</category>
    </item>
    <item>
      <title>Python. Project Structure (VIII)</title>
      <dc:creator>Raül Martínez i Peris</dc:creator>
      <pubDate>Sat, 04 Oct 2025 15:35:23 +0000</pubDate>
      <link>https://dev.to/elferrer/python-project-structure-viii-17ha</link>
      <guid>https://dev.to/elferrer/python-project-structure-viii-17ha</guid>
      <description>&lt;h4&gt;
  
  
  Índice:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Sección A. Estructurar un proyecto: &lt;a href="https://dev.to/elferrer/python-i-4cp6"&gt;apartado I&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-estructurar-un-proyecto-ii-5d28"&gt;apartado II&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-project-structure-iii-4gib"&gt;apartado III&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-project-structure-iv-1jke"&gt;apartado IV&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-project-structure-v-h72"&gt;apartado V&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-project-structure-vi-5a00"&gt;apartado VI&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-project-structure-vii-2ggd"&gt;apartado VII&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-project-structure-viii-17ha"&gt;apartado VIII&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-project-structure-ix-3d80"&gt;apartado IX&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-project-structure-x-82d"&gt;apartado X&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Para crear la aplicación &lt;code&gt;.exe&lt;/code&gt; para Windows, vamos a necesitar lanzar PyInstaller a través de &lt;em&gt;GitHub Actions&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;¿Porqué GitHub? GitHub nos permite lanzar gratuitamente hasta 2.000 minutos de cómputo, incluyendo también una &lt;em&gt;FreeTier&lt;/em&gt; de Windows.&lt;/p&gt;

&lt;p&gt;¿Porqué no se hizo todo con GitHub? La mejor forma de aprender cómo integrar diferentes plataforma es haciéndolo, por lo que aprovechamos que ambas plataformas tienen planes gratuitos para mostrar como integrarlas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preparar el repositorio de GitHub
&lt;/h2&gt;

&lt;p&gt;Vamos a &lt;em&gt;GitHub&lt;/em&gt; y creamos un nuevo repositorio para lanzar el &lt;em&gt;pipeline&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Una vez creado el repositorio, en el icono de usuario (arriba a la derecha) pulsa &lt;code&gt;Settings&lt;/code&gt; en el desplegable.&lt;/p&gt;

&lt;p&gt;Nos aparecerá a la izquierda, al final de todos los &lt;em&gt;settings&lt;/em&gt; (abajo), el apartado &lt;code&gt;Developer setting&lt;/code&gt;, tras abrir &lt;code&gt;Personal access tokens&lt;/code&gt; pulsamos sobre &lt;code&gt;Tokens (clasic)&lt;/code&gt; y nos creamos un nuevo &lt;em&gt;token&lt;/em&gt; haciendo &lt;em&gt;click&lt;/em&gt; en:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Generate new token&lt;/code&gt; &amp;gt; &lt;code&gt;Generate new token (classic)&lt;/code&gt;, y rellenamos:

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Note&lt;/em&gt;: ponemos un &lt;em&gt;alias&lt;/em&gt; identificativo.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Expiration&lt;/em&gt;: seleccionamos cuanto tiempo queremos que dure.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Select scopes&lt;/em&gt;, seleccionamos los siguientes:&lt;/li&gt;
&lt;li&gt;repo (todos)&lt;/li&gt;
&lt;li&gt;workflow&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Pulsamos en &lt;code&gt;Generate token&lt;/code&gt;.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Atención:&lt;/em&gt;&lt;/strong&gt; Únicamente nos mostrará una vez el token, nunca volverás a verlo, guárdalo en tu almacén de contraseñas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preparación de los &lt;em&gt;pipelines&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;En esta parte vamos a indicar los archivos necesarios, pero será en el artículo siguiente donde haremos una breve explicación de las partes interesantes a tener en cuenta.&lt;/p&gt;

&lt;p&gt;Añadimos a nuestro repo los siguientes archivos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Actualizamos &lt;code&gt;requirements-extra-windows.txt&lt;/code&gt; añadiendo:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PyInstaller
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Creamos el 'github-build' que necesitaremos para &lt;em&gt;GitHub&lt;/em&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# ci/github-build.yml
name: Build EXE

on:
  push:
    branches:
      - __BUILD_BRANCH__

permissions:
  contents: write

jobs:
  build:
    runs-on: windows-latest
    steps:
      # 1. Clonar el repositorio
      - name: Checkout project
        uses: actions/checkout@v4

      # 2. **Verificación entorno**
      - name: Check for .env.production file
        id: check_env
        shell: pwsh
        run: |
          if (-not (Test-Path ".env.production")) {
            Write-Error "Error: No existe el archivo '.env.production'. Recuerde que la aplicación se genera con el entorno de producción."
            exit 1
          }

      # 3. Instalar Python 3.12
      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"

      # 4. Crear y activar entorno virtual
      - name: Create virtual environment
        shell: pwsh
        run: |
          python -m venv venv
          .\venv\Scripts\Activate.ps1
          python -m pip install --upgrade pip wheel setuptools

      # 5. Instalar dependencias dentro del venv
      - name: Install dependencies
        shell: pwsh
        run: |
          .\venv\Scripts\Activate.ps1
          # Instalación de requisitos explícitos
          pip install PyQt6 PyQt6-WebEngine
          pip install nicegui
          pip install pydantic-settings
          pip install pydantic
          pip install pywebview
          # Resto de requisitos
          pip install -r requirements.txt -r requirements-extra-windows.txt
          # PyInstaller al final
          pip install pyinstaller
          pip install --upgrade pyinstaller pyinstaller-hooks-contrib

      # 6. Verificar paquetes instalados
      - name: Verify key packages
        shell: pwsh
        run: |
          .\venv\Scripts\Activate.ps1
          python -c "import PyQt6; print('PyQt6 OK')"
          python -c "import PyQt6.QtWebEngineWidgets; print('WebEngineWidgets OK')"
          python -c "import nicegui; print('NiceGUI OK')"

      # 7. Comprobar DLLs de Qt6
      - name: Verify Qt6 DLLs
        shell: pwsh
        run: |
          .\venv\Scripts\Activate.ps1
          $pyqt6_path = (python -c "import PyQt6; print(PyQt6.__path__[0])")
          $qt6_base = Join-Path $pyqt6_path "Qt6"
          if (Test-Path "$qt6_base\bin") { echo "Qt6 bin exists"; dir "$qt6_base\bin" } else { echo "Qt6 bin not found" }
          if (Test-Path "$qt6_base\plugins") { echo "Qt6 plugins exist"; dir "$qt6_base\plugins" } else { echo "Qt6 plugins not found" }

      # 8. Instalar WebView2 runtime (para pywebview)
      - name: Install WebView2 runtime
        shell: pwsh
        run: |
          Invoke-WebRequest -Uri "https://go.microsoft.com/fwlink/p/?LinkId=2124703" -OutFile "MicrosoftEdgeWebView2Setup.exe"
          write-host "Lanzando MicrosoftEdgeWebView2Setup.exe en modo desatentido (silent)..."
          Start-Process .\MicrosoftEdgeWebView2Setup.exe -ArgumentList "/silent","/install" -Wait

      # 9. Compilar con PyInstaller
      - name: Compile app
        shell: pwsh
        run: |
          .\venv\Scripts\Activate.ps1
          pyinstaller app/main.spec --noconfirm
          echo "Build finished. Checking dist/"
          dir dist

      # 10. Copiar DLLs y plugins de Qt6 al dist
      - name: Copy Qt6 runtime dependencies
        shell: pwsh
        run: |
          .\venv\Scripts\Activate.ps1
          $pyqt6_path = (python -c "import PyQt6; print(PyQt6.__path__[0])")
          $QT6_PATH = Join-Path $pyqt6_path "Qt6"
          $DEST = "dist\la_fragua"
          Write-Host "Copying Qt6 from $QT6_PATH to $DEST"
          Copy-Item "$QT6_PATH\bin\*.dll" -Destination "$DEST\PyQt6\Qt6\bin" -Recurse -Force -ErrorAction SilentlyContinue
          Copy-Item "$QT6_PATH\plugins" -Destination "$DEST\PyQt6\Qt6" -Recurse -Force -ErrorAction SilentlyContinue
          Copy-Item "$QT6_PATH\resources" -Destination "$DEST\PyQt6\Qt6" -Recurse -Force -ErrorAction SilentlyContinue
          if (Test-Path "$QT6_PATH\translations") {
            Copy-Item "$QT6_PATH\translations" -Destination "$DEST\PyQt6\Qt6" -Recurse -Force
          }

      # 11. Publicar artefacto en release
      - name: Release build artifact
        uses: softprops/action-gh-release@v1
        with:
          tag_name: "__BUILD_BRANCH__-${{ github.run_number }}"
          generate_release_notes: true
          files: dist/*.exe
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Ahora necesitamos crear el archivo &lt;code&gt;spec&lt;/code&gt; para la configuración de creación del &lt;em&gt;.exe&lt;/em&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# app/main.spec
# main.spec para compilación en windows
# -*- mode: python ; coding: utf-8 -*-

import os
import sys
import PyQt6
import PyQt6.QtWebEngineWidgets
import nicegui
from PyInstaller.utils.hooks import collect_all, collect_data_files, collect_submodules

# ---- Carpeta base del proyecto ----
project_dir = os.getcwd()
app_dir = os.path.join(project_dir, "app")
hookspath = [os.path.join(project_dir, 'extra-hooks')]

# ---- Recolectar archivos del proyecto ----
def collect_all_data(src_folder):
    datas = []
    for root, dirs, files in os.walk(src_folder):
        for file in files:
            if file.endswith('.pyc') or file.endswith('.env.production'):
                continue
            full_path = os.path.join(root, file)
            rel_path = os.path.relpath(root, src_folder)
            target_path = os.path.join(rel_path)
            datas.append((full_path, target_path))
    return datas

datas = collect_all_data(app_dir)

# ---- Qt6 DLLs y plugins ----
site_packages = sys.base_prefix + r"\Lib\site-packages"
qt6_base = os.path.join(site_packages, "PyQt6", "Qt6")

# DLLs (bin)
qt_bin = os.path.join(qt6_base, "bin")
if os.path.exists(qt_bin):
    datas += [(os.path.join(qt_bin, f), "PyQt6/Qt6/bin") for f in os.listdir(qt_bin) if f.endswith(".dll")]

# Plugins (platforms, imageformats, etc.)
qt_plugins = os.path.join(qt6_base, "plugins")
for root, dirs, files in os.walk(qt_plugins):
    for file in files:
        full_path = os.path.join(root, file)
        rel_path = os.path.relpath(root, qt6_base)
        datas.append((full_path, f"PyQt6/Qt6/{rel_path}"))

# ---- NiceGUI recursos estáticos ----
datas += collect_data_files("nicegui", subdir="static")
datas += collect_data_files("nicegui", subdir="templates")

# Aquí cambiamos a .env.production
datas += [(os.path.join(project_dir, '.env.production'), '.')]

# ---- Icono opcional ----
icon_path = os.path.join(app_dir, 'assets', 'icon.ico')
if not os.path.exists(icon_path):
    icon_path = None

# ---- Recoger todos los submódulos para pydantic y pydantic_settings ----
hidden_imports = collect_submodules('pydantic') + collect_submodules('pydantic_settings') + ["PyQt6.QtWebEngineWidgets"]

# ---- Análisis ----
a = Analysis(
    [os.path.join(app_dir, "main.py")],
    pathex=[project_dir],
    binaries=[],
    datas=datas,
    hiddenimports=[
        "PyQt6.QtWebEngineWidgets",
        "PyQt6.QtWebEngineCore",
        "PyQt6.QtWebChannel",
        "PyQt6.QtPositioning",
    ] + hidden_imports,
    hookspath=hookspath,
    runtime_hooks=[],
    excludes=[],
    win_no_prefer_redirects=False,
    win_private_assemblies=False,
    cipher=None,
    noarchive=False,
)

pyz = PYZ(a.pure, a.zipped_data, cipher=None)

exe = EXE(
    pyz,
    a.scripts,
    a.binaries,
    a.zipfiles,
    a.datas,
    name="la_fragua.exe",
    debug=False,
    strip=False,
    upx=True,
    console=True,
    icon=icon_path,
)

coll = COLLECT(
    exe,
    a.binaries,
    a.zipfiles,
    a.datas,
    strip=False,
    upx=True,
    name="la_fragua",
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;También necesitamos el archivo 'hook-pydantic':
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# extra-hooks/hook-pydantic.py
from PyInstaller.utils.hooks import collect_submodules

hiddenimports = collect_submodules('pydantic') + collect_submodules('pydantic_settings')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Y finalmente, añadimos a 'gitlab-ci' el &lt;code&gt;stage&lt;/code&gt; necesario (línea 3-4):
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  - build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;a continuación del &lt;em&gt;stage&lt;/em&gt; 'test' le agregamos una nueva tarea:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;launch-build-exe:
  stage: build
  image: alpine:latest
  before_script:
    - apk add --no-cache git zip unzip curl bash
    - curl --silent "https://gitlab.com/gitlab-org/incubation-engineering/mobile-devops/download-secure-files/-/raw/main/installer" | bash
    # descarga de los archivos protegidos
    - download-secure-files
    - cp .secure_files/.env .
    - cp .secure_files/.env.production .
  script:
    # empaquetar el código excluyendo .git
    - zip -r project.zip . -x ".git/*" -x "tests/*" -x "KANBAN.md" -x "README.md" -x ".gitlab-ci.yml" -x ".gitignore"

    # configurar git
    - git config --global user.email "$GITLAB_CI_EMAIL"
    - git config --global user.name "$GITLAB_CI_USER"

    # clonar el repo de GitHub
    - git clone https://oauth2:${GITHUB_TOKEN}@github.com/${GITHUB_USER}/${GITHUB_REPO}.git
    - cd ${GITHUB_REPO}
    - git checkout -B $BUILD_BRANCH
    - rm -rf *

    # mover proyecto
    - mv ../project.zip .
    - unzip project.zip
    - rm project.zip

    # añadir workflow con datos dinámicos
    - mkdir -p .github/workflows
    - sed -e "s|__BUILD_BRANCH__|$BUILD_BRANCH|g" -e "s|__CI_PROJECT_NAME__|$CI_PROJECT_NAME|g" ./ci/github-build.yml &amp;gt; .github/workflows/build.yml
    - mv ./ci/README.github.md README.md
    - rm -r ./ci

    # commit &amp;amp; push
    - git add .
    - git commit -m "Trigger build from GitLab CI ${BUILD_BRANCH}"
    - git push origin $BUILD_BRANCH --force
  rules:
    - if: '$CI_COMMIT_BRANCH == "main" &amp;amp;&amp;amp; $CI_COMMIT_MESSAGE =~ /^\[build-exe\]/'
      when: always
    - when: manual
  allow_failure: true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configuración del entorno de &lt;em&gt;Gitlab&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;Una vez actualizados los ficheros preparamos el entorno de Gitlab. Para ello, desde &lt;em&gt;Gitlab&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Settings&lt;/code&gt; &amp;gt; &lt;code&gt;CI/CD Settings&lt;/code&gt; &amp;gt; &lt;code&gt;CI/CD Variables&lt;/code&gt;, añadimos la variables:

&lt;ul&gt;
&lt;li&gt;BUILD_BRANCH (contenido: &lt;em&gt;un nombre descriptivo para tu rama en GitHub&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;GITHUB_REPO (contenido: &lt;em&gt;el nombre de tu repositorio en GitHub&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;GITHUB_USER (contenido: &lt;em&gt;tu usuario de GitHub&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;GITLAB_CI_EMAIL (contenido: &lt;em&gt;tu correo electronico&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;GITLAB_CI_USER (contenido: &lt;em&gt;un alias descriptivo&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;GITHUB_TOKEN (contenido: &lt;em&gt;tu token de GitHub&lt;/em&gt;)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Actualizamos/añadimos los archivos protegidos:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Settings&lt;/code&gt; &amp;gt; &lt;code&gt;CI/CD Settings&lt;/code&gt; &amp;gt; &lt;code&gt;Secure files&lt;/code&gt;:&lt;/li&gt;
&lt;li&gt;.env&lt;/li&gt;
&lt;li&gt;.env.production&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Una vez que has configurado totalmente las variables y archivos protegidos, ya puedes subir la rama a &lt;em&gt;Gitlab&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git add .
git commit -m 'Virtualización'
git push -u origin 'release/000-virtualization'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Volvemos a &lt;em&gt;Gitlab&lt;/em&gt; realizamos el &lt;em&gt;Merge Request&lt;/em&gt; y lo fusionamos.&lt;/p&gt;

&lt;p&gt;Una vez terminado solo nos queda actualizar nuestra rama local de 'main':&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git checkout main
git pull
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Comprobación
&lt;/h2&gt;

&lt;p&gt;Si todo ha funcionado correctamente, en tu cuenta de &lt;em&gt;GitHub&lt;/em&gt; se habrá lanzado una &lt;em&gt;Action&lt;/em&gt; sobre tu repositorio, que te habrá generado un artefacto y un tag a dicho artefacto.&lt;/p&gt;

&lt;p&gt;Para ver el artefacto debes pinchar sobre "Tag" y una vez dentro verás que también tienes "Releases", dentro de las cuales están los artefactos generados.&lt;/p&gt;

&lt;h2&gt;
  
  
  Apuntes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;En BUILD_BRANCH puedes poner, por ejemplo: 'el_repositorio-build_artifact'.&lt;/li&gt;
&lt;li&gt;En GITLAB_CI_EMAIL, puedes poner tu &lt;em&gt;email&lt;/em&gt; o puedes utilizar un &lt;em&gt;email&lt;/em&gt; que te indica &lt;em&gt;GitHub&lt;/em&gt;. Lo tienes en los &lt;code&gt;Settings&lt;/code&gt; de usuario, en el apartado &lt;code&gt;Emails&lt;/code&gt; &amp;gt; &lt;code&gt;Keep my email addresses private&lt;/code&gt;, donde tras activarlo leerás en la explicación &lt;em&gt;"We'll remove your public profile email and use [&lt;a href="mailto:...@users.noreply.github.com"&gt;...@users.noreply.github.com&lt;/a&gt;]"&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Enlaces
&lt;/h2&gt;

&lt;p&gt;Repositorio del proyecto:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/elferrer/la_fragua.git" rel="noopener noreferrer"&gt;https://gitlab.com/elferrer/la_fragua.git&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Siguiente artículo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/elferrer/python-project-structure-ix-3d80"&gt;Sección A. Estructurar un proyecto: apartado IX&lt;/a&gt;: Comentarios sobre la creación del &lt;code&gt;.exe&lt;/code&gt; de Windows.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>ci</category>
      <category>cd</category>
      <category>docker</category>
    </item>
    <item>
      <title>Python. Project Structure (VII)</title>
      <dc:creator>Raül Martínez i Peris</dc:creator>
      <pubDate>Sat, 04 Oct 2025 15:19:19 +0000</pubDate>
      <link>https://dev.to/elferrer/python-project-structure-vii-2ggd</link>
      <guid>https://dev.to/elferrer/python-project-structure-vii-2ggd</guid>
      <description>&lt;h4&gt;
  
  
  Índice:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Sección A. Estructurar un proyecto: &lt;a href="https://dev.to/elferrer/python-i-4cp6"&gt;apartado I&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-estructurar-un-proyecto-ii-5d28"&gt;apartado II&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-project-structure-iii-4gib"&gt;apartado III&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-project-structure-iv-1jke"&gt;apartado IV&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-project-structure-v-h72"&gt;apartado V&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-project-structure-vi-5a00"&gt;apartado VI&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-project-structure-vii-2ggd"&gt;apartado VII&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-project-structure-viii-17ha"&gt;apartado VIII&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-project-structure-ix-3d80"&gt;apartado IX&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-project-structure-x-82d"&gt;apartado X&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Como hemos comentado en anteriormente, hemos cometido un par de errores en los archivos &lt;code&gt;Dockerfile&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Ahora debemos corregirlo, pero, queremos hacerlo en la rama correspondiente y llevar los cambios a &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  ¿Qué ocurre si no estamos en un estado 'limpio'?
&lt;/h2&gt;

&lt;p&gt;Antes de continuar, como es habitual, debes estar sin ningún cambio en el repositorio: nunca debes empezar a trabajar teniendo código a medias, en ninguna rama.&lt;/p&gt;

&lt;p&gt;Si estás trabajando y surge una urgencia que te obliga a dejar a medias un trabajo, tienes varias opciones:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Guardar el trabajo para continuar después sin ensuciar el histórico. Esta es la mejor opción.&lt;/li&gt;
&lt;li&gt;Descartar lo trabajado.&lt;/li&gt;
&lt;li&gt;Crear un commit (indicando claramente que el trabajo no está terminado). Esto es una &lt;strong&gt;&lt;em&gt;mala opción&lt;/em&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Veamos la opción &lt;strong&gt;1&lt;/strong&gt;, cómo guardar el trabajo sin hacer &lt;em&gt;commit&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git add .
git stash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esto realiza un 'commit interno' que no existe en el histórico de las ramas.&lt;/p&gt;

&lt;p&gt;Para consultar los &lt;em&gt;commit internos&lt;/em&gt; que tienes en &lt;em&gt;stash&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git stash list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para recuperar el último &lt;em&gt;commit interno&lt;/em&gt; que tienes en el &lt;em&gt;stash&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git stash apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hay más opciones muy interesantes, revisa la documentación de git.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sincronizar la copia local
&lt;/h2&gt;

&lt;p&gt;Sabiendo que no tenemos código a medias, lo primero es tener actualizadas las ramas involucradas.&lt;/p&gt;

&lt;p&gt;Nos situamos en &lt;code&gt;main&lt;/code&gt; y la actualizamos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git checkout main
git pull
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nos situamos en &lt;code&gt;release/000-virtualization&lt;/code&gt; y la actualizamos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git checkout 'release/000-virtualization'
git pull
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Realizar los cambios y hacer el &lt;em&gt;commit&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;Ahora realizaremos las correcciones de los errores que cometimos.&lt;/p&gt;

&lt;p&gt;En el archivo &lt;code&gt;Dockerfile&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Busca &lt;code&gt;APP_FOLDER&lt;/code&gt; y sustituyelo por &lt;code&gt;APP_PATH&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;En el archivo &lt;code&gt;Dockerfile.windows&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Busca &lt;code&gt;APP_FOLDER&lt;/code&gt; y sustituyelo por &lt;code&gt;APP_PATH&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Busca &lt;code&gt;APP_VIRTUALIZATION&lt;/code&gt; y sustituyelo por &lt;code&gt;APP_ENV&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ahora realizamos el &lt;em&gt;commit&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git add .
git commit -m 'Correcciones en los archivos Dockerfile'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Preparar la rama y subir los cambios
&lt;/h2&gt;

&lt;p&gt;Primero nos traemos la rama &lt;code&gt;main&lt;/code&gt; a la nuestra:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git merge main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Si existe algún conflicto git te lo avisará. Si esto ocurre tendrás que resolverlo, y hacer un nuevo &lt;em&gt;commit&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Ahora debemos subir los cambios a &lt;code&gt;origin&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git push origin 'release/000-virtualization'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Te estarás preguntando porqué hicimos el &lt;code&gt;merge&lt;/code&gt; después de hacer los cambios y el &lt;code&gt;commit&lt;/code&gt;. La explicación es sencilla: queremos tener en nuestra rama lo último que entró en &lt;code&gt;main&lt;/code&gt; porque después debemos realizar el &lt;code&gt;merge request&lt;/code&gt; y para que no haya problemas añadidos deberíamos tener lo último. Por ello, cuanto más tiempo pasa entre tu &lt;code&gt;merge&lt;/code&gt; de &lt;code&gt;main&lt;/code&gt; y tu &lt;code&gt;push&lt;/code&gt; a &lt;code&gt;origin&lt;/code&gt; , más posibilidades de que no sea lo último. Yo personalmente hago un &lt;code&gt;merge&lt;/code&gt; antes de empezar y otro después, lo cual me garantiza trabajar sobre lo último y asegurarme que es lo último con lo que hago la petición de MR.&lt;/p&gt;

&lt;h2&gt;
  
  
  Crear el &lt;em&gt;Merge Request&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;Esta parte ya sabes como funciona, lo vimos en un artículo anterior. Haz el MR y después descárgate a &lt;code&gt;main&lt;/code&gt; los cambios.&lt;/p&gt;

&lt;h2&gt;
  
  
  Apuntes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;em&gt;Merge&lt;/em&gt; vs &lt;em&gt;Rebase&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;En &lt;em&gt;git&lt;/em&gt; hay una regla de oro: nunca hagas un &lt;em&gt;rebase&lt;/em&gt; en ramas compartidas o públicas.&lt;/p&gt;

&lt;p&gt;Estas son las razones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Preservación del historial (acción no destructiva): con &lt;code&gt;git merge&lt;/code&gt; preservamos el historial de forma cronológica y real.&lt;/li&gt;
&lt;li&gt;Seguridad en el trabajo en equipo: la utilización de &lt;code&gt;git merge&lt;/code&gt; no reescribe los &lt;em&gt;commit&lt;/em&gt; por lo que es fácil llevar un seguimiento rápido y limpio. Sin embargo, el uso de &lt;code&gt;git rebase&lt;/code&gt; reescribe el historial de la rama, por lo que en cualquier otro clonado existente del repositorio los ID's de los commits serán diferentes, provocando en el resto del equipo un considerable costo de tiempo revisando lo ocurrido.&lt;/li&gt;
&lt;li&gt;Claridad en las auditorías de código: cuando utilizamos &lt;code&gt;git merge&lt;/code&gt;, la utilización de herramientas como &lt;code&gt;git bisect&lt;/code&gt; se facilita enormemente al mostrar exactamente qué ocurrió y cuando.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Enlaces
&lt;/h2&gt;

&lt;p&gt;Repositorio del proyecto:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/elferrer/la_fragua.git" rel="noopener noreferrer"&gt;https://gitlab.com/elferrer/la_fragua.git&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Enlaces de interés:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sobre los entornos: en &lt;a href="https://gitlab.com/elferrer/la_fragua/-/blob/main/README.md?ref_type=heads#docker-compose" rel="noopener noreferrer"&gt;https://gitlab.com/elferrer/la_fragua/-/blob/main/README.md?ref_type=heads#docker-compose&lt;/a&gt; encontrarás información sobre cómo utilizar los diferentes entornos de desarrollo/producción en Linux/Windows.&lt;/li&gt;
&lt;li&gt;Git stash: &lt;a href="https://git-scm.com/docs/git-stash" rel="noopener noreferrer"&gt;https://git-scm.com/docs/git-stash&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Siguiente artículo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/elferrer/python-project-structure-viii-17ha"&gt;Sección A. Estructurar un proyecto: apartado VIII&lt;/a&gt;: Creación del &lt;code&gt;.exe&lt;/code&gt; de Windows.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>ci</category>
      <category>cd</category>
      <category>docker</category>
    </item>
    <item>
      <title>Python. Project Structure (VI)</title>
      <dc:creator>Raül Martínez i Peris</dc:creator>
      <pubDate>Sat, 04 Oct 2025 15:04:11 +0000</pubDate>
      <link>https://dev.to/elferrer/python-project-structure-vi-5a00</link>
      <guid>https://dev.to/elferrer/python-project-structure-vi-5a00</guid>
      <description>&lt;h4&gt;
  
  
  Índice:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Sección A. Estructurar un proyecto: &lt;a href="https://dev.to/elferrer/python-i-4cp6"&gt;apartado I&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-estructurar-un-proyecto-ii-5d28"&gt;apartado II&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-project-structure-iii-4gib"&gt;apartado III&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-project-structure-iv-1jke"&gt;apartado IV&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-project-structure-v-h72"&gt;apartado V&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-project-structure-vi-5a00"&gt;apartado VI&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-project-structure-vii-2ggd"&gt;apartado VII&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-project-structure-viii-17ha"&gt;apartado VIII&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-project-structure-ix-3d80"&gt;apartado IX&lt;/a&gt;, &lt;a href="https://dev.to/elferrer/python-project-structure-x-82d"&gt;apartado X&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;En este apartado crearemos lo necesario para trabajar con la virtualización de entornos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Crear una nueva rama
&lt;/h2&gt;

&lt;p&gt;Empezamos con la creación de la rama &lt;code&gt;release/000-virtualization&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git checkout -b 'release/000-virtualization'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ahora actualizamos el archivo &lt;code&gt;.gitignore&lt;/code&gt; añadiendo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.env.production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Docker
&lt;/h2&gt;

&lt;p&gt;Empezaremos añadiendo el archivo &lt;code&gt;.dockerignore&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_temp_/
.git
.gitignore
.env*
.vscode/
.venv/
.pytest*
tests/
Dockerfile
compose.yml
compose.override.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ahora prepararemos los entornos para Linux y Windows por separado.&lt;/p&gt;

&lt;h3&gt;
  
  
  Docker en el entorno Linux
&lt;/h3&gt;

&lt;p&gt;Creamos el archivo Dockerfile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Dockerfile
FROM python:3.12-slim AS base

# Configuración de entorno
ARG APP_PATH=/opt/la_fragua
ENV APP_PATH=${APP_PATH}
ARG APP_ENV=production
ENV APP_ENV=${APP_ENV}
ARG APP_PORT=8080
ENV APP_PORT=${APP_PORT}

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    POETRY_VIRTUALENVS_CREATE=false \
    PATH="/root/.local/bin:$PATH" \
    DISPLAY=:99 \
    QT_QPA_PLATFORM=offscreen

# Crear directorio de la aplicación
WORKDIR ${APP_PATH}

# Instalar dependencias del sistema necesarias para nicegui (qt, chrome, etc.)
RUN apt-get update &amp;amp;&amp;amp; apt-get install -y \
    git curl build-essential \
    libglib2.0-0 libnss3 libx11-6 libxcomposite1 \
    libxdamage1 libxrandr2 libxkbcommon0 libxshmfence1 \
    libasound2 libatk1.0-0 libatk-bridge2.0-0 libcups2 \
    libdrm2 libxext6 libgbm1 libxfixes3 libxrender1 \
    xvfb x11vnc fluxbox wmctrl \
    &amp;amp;&amp;amp; rm -rf /var/lib/apt/lists/*

# ---- desarrollo ----
FROM base AS dev
COPY requirements.txt .
COPY requirements-dev.txt .
RUN pip install --upgrade pip &amp;amp;&amp;amp; pip install -r requirements.txt -r requirements-dev.txt
CMD ["python3", "-m", "app.main"]

# ---- producción ----
FROM base AS production
COPY requirements.txt .
RUN pip install --upgrade pip &amp;amp;&amp;amp; pip install -r requirements.txt
COPY . ${APP_FOLDER}
EXPOSE ${APP_PORT}
RUN echo '#!/bin/bash\nXvfb :99 -screen 0 1024x768x24 &amp;gt; /dev/null 2&amp;gt;&amp;amp;1 &amp;amp;\npython3 -m app.main' &amp;gt; /opt/start.sh &amp;amp;&amp;amp; \
    chmod +x /opt/start.sh
CMD ["/opt/start.sh"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Una vez que tenemos el archivo &lt;code&gt;Dockerfile&lt;/code&gt; vamos a preparar los archivos 'compose' para automatizar la creación y ejecución de los entornos.&lt;/p&gt;

&lt;p&gt;Creamos el archivo compose.yml:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# compose.yml para producción
services:
  la_fragua:
    image: "${DOCKER_LOGIN}/${APP_NAME}-linux:latest"
    build:
      context: .
      dockerfile: "Dockerfile"
      target: production
    env_file:
      - .env.production
    ports:
      - "${APP_PORT}:8080"
    environment:
      - APP_ENV=container
      - APP_PATH=/opt/la_fragua
    restart: always
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creamos el archivo compose.override.yml:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# compose.override.yml para dev
services:
  la_fragua:
    image: "${APP_NAME}-linux:dev"
    build:
      context: .
      dockerfile: "Dockerfile"
      target: dev
    env_file:
      - .env
    ports:
      - "${APP_PORT}:8080"
    environment:
      - APP_ENV=container
      - APP_PATH=/opt/la_fragua
    volumes:
      - .:/opt/la_fragua
    stdin_open: true
    tty: true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Docker en el entorno Windows
&lt;/h2&gt;

&lt;p&gt;Añadimos al archivo &lt;code&gt;.dockerignore&lt;/code&gt; el siguiente contenido:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dockerfile.windows
compose.windows.yml
compose.windows.override.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creamos el archivo Dockerfile.windows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Dockerfile.windows
FROM mcr.microsoft.com/windows/servercore:ltsc2022 AS base

# Shell Powershell
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop';"]

# Instalar Python
RUN Invoke-WebRequest -UseBasicParsing https://www.python.org/ftp/python/3.12.0/python-3.12.0-amd64.exe -OutFile python-installer.exe; \
    Start-Process python-installer.exe -Wait -ArgumentList '/quiet', 'InstallAllUsers=1', 'PrependPath=1', 'DefaultAllUsersTargetDir=C:\Python312'; \
    Remove-Item python-installer.exe -Force

# Actualizar pip
RUN python -m pip install --upgrade pip --disable-pip-version-check

# Configuración de entorno
ARG APP_PATH="C:/la_fragua"
ENV APP_PATH=${APP_PATH}
ARG APP_ENV=production
ENV APP_ENV=${APP_ENV}
ARG APP_PORT=8080
ENV APP_PORT=${APP_PORT}

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    APP_VIRTUALIZATION=container \
    QT_QPA_PLATFORM=minimal

# Crear directorio de la aplicación
WORKDIR ${APP_PATH}

# ---------- Dev stage ----------
FROM base AS dev
COPY requirements.txt .
COPY requirements-dev.txt .
RUN pip install --no-cache-dir -r requirements.txt -r requirements-dev.txt
CMD ["python", "-m", "app.main"]

# ---------- Production stage ----------
FROM base AS production
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . ${APP_FOLDER}
EXPOSE ${APP_PORT}
CMD ["python", "-m", "app.main"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creamos el archivo compose.windows.yml&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# compose.windows.yml para producción
services:
  la_fragua:
    image: "${DOCKER_LOGIN}/${APP_NAME}-windows:latest"
    build:
      context: .
      dockerfile: "Dockerfile.windows"
      target: production
    env_file:
      - .env.production
    ports:
      - "${APP_PORT}:8080"
    environment:
      - APP_ENV=container
      - APP_PATH="C:/la_fragua"
    restart: always
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creamos el archivo compose.windows.override.yml&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# compose.windows.override.yml para dev
services:
  la_fragua:
    image: "${APP_NAME}-windows:dev"
    build:
      context: .
      dockerfile: "Dockerfile.windows"
      target: dev
    env_file:
      - .env
    ports:
      - "${APP_PORT}:8080"
    environment:
      - APP_ENV=container
      - APP_PATH="C:/la_fragua"
    volumes:
      - .:C:/la_fragua
    stdin_open: true
    tty: true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ya tenemos preparados los entornos docker de Linux y Windows.&lt;/p&gt;

&lt;p&gt;Hemos cometido un par de errores en los Dockerfile para obligarnos a realizar una corrección. En el siguiente artículo trabajaremos sobre ello.&lt;/p&gt;

&lt;h2&gt;
  
  
  Apuntes
&lt;/h2&gt;

&lt;p&gt;En los archivos &lt;code&gt;Dockerfile&lt;/code&gt; habrás visto que utilizamos &lt;code&gt;FROM python:3.12-slim AS base&lt;/code&gt; al inicio, y posteriormente &lt;code&gt;FROM base AS dev&lt;/code&gt; y &lt;code&gt;FROM base AS production&lt;/code&gt;. Esto, unido al &lt;code&gt;target&lt;/code&gt; de los archivos &lt;code&gt;compose&lt;/code&gt; nos define qué parte del Dockerfile está generándose. Con esta dinámica hemos conseguido no repetir información en innecesariamente. En el siguiente artículo continuamos trabajando sobre los entornos.&lt;/p&gt;

&lt;p&gt;En los archivos &lt;code&gt;compose&lt;/code&gt; verás que en producción tenemos la variable &lt;code&gt;DOCKER_LOGIN&lt;/code&gt; pero en desarrollo no lo está. Esto tiene una razón: la imagen docker que se genere para producción querremos poder llevarla al &lt;code&gt;hub&lt;/code&gt; de docker, pero la imagen de desarrollo no debemos llevarla nunca al &lt;code&gt;hub&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enlaces
&lt;/h2&gt;

&lt;p&gt;Repositorio del proyecto:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/elferrer/la_fragua.git" rel="noopener noreferrer"&gt;https://gitlab.com/elferrer/la_fragua.git&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Enlaces de interés:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multi-stage builds: &lt;a href="https://docs.docker.com/build/building/multi-stage/" rel="noopener noreferrer"&gt;https://docs.docker.com/build/building/multi-stage/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Merge Compose files: &lt;a href="https://docs.docker.com/compose/how-tos/multiple-compose-files/merge/" rel="noopener noreferrer"&gt;https://docs.docker.com/compose/how-tos/multiple-compose-files/merge/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Siguiente artículo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/elferrer/python-project-structure-vii-2ggd"&gt;Sección A. Estructurar un proyecto: apartado VII&lt;/a&gt;: Corregir errores y no morir en el intento.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>ci</category>
      <category>cd</category>
      <category>docker</category>
    </item>
  </channel>
</rss>
