Desarrollando con Quarkus en VS Code usando Dev Containers

¡Hola developer 👋🏻!
A principios de semana te contaba cómo estoy desarrollando actualmente mis aplicaciones con Spring Boot en Visual Studio Code gracias a Dev Containers, y todo lo que podemos configurar como parte del entorno de desarrollo.

Pero… ¿y si tu framework preferido no es Spring Boot sino Quarkus?
Entonces este artículo es para ti 😉

En esta ocasión quiero compartir la configuración de Dev Container que estoy utilizando para proyectos con Quarkus. 🚨 Aviso: me ha dado un poquito más de guerra que la de Spring Boot, pero después de varios ajustes, esta es la que estoy usando ahora mismo y me funciona perfectamente.

🎥 Antes de entrar en la configuración, si todavía no estás familiarizado con Dev Containers o quieres ver cómo los uso en mi día a día con Visual Studio Code, te dejo por aquí un vídeo donde lo explico paso a paso 👇🏻

📁 .devcontainer/devcontainer.json

Esta es la configuración completa del Dev Container:

{
  "name": "Quarkus Dev Environment",
  "dockerComposeFile": "compose.yml",
  "service": "app",
  "workspaceFolder": "/workspace",
  "customizations": {
    "vscode": {
      "extensions": [
        "redhat.java",
        "vscjava.vscode-java-pack",
        "vscjava.vscode-gradle",
        "redhat.vscode-quarkus",
        "redhat.vscode-microprofile",
        "redhat.vscode-yaml",
        "mtxr.sqltools",
        "mtxr.sqltools-driver-pg",
        "ms-azuretools.vscode-docker",
        "humao.rest-client"
      ],
      "settings": {
        "java.configuration.updateBuildConfiguration": "automatic",
        "java.server.launchMode": "Standard",
        "quarkus.tools.starter.api.base": "https://code.quarkus.io",
        "editor.formatOnSave": true,
        "editor.codeActionsOnSave": {
          "source.organizeImports": "explicit"
        },
        "[java]": {
          "editor.defaultFormatter": "redhat.java"
        },
        "sqltools.connections": [
          {
            "name": "🐘 Sports DB (PostgreSQL)",
            "driver": "PostgreSQL",
            "server": "localhost",
            "port": 5432,
            "database": "sports_db",
            "username": "sports_user",
            "password": "sports_pass"
          }
        ]
      }
    }
  },
  "forwardPorts": [
    8080,
    5432
  ],
  "portsAttributes": {
    "8080": {
      "label": "🚀 Quarkus App",
      "onAutoForward": "notify"
    },
    "5432": {
      "label": "🐘 PostgreSQL",
      "onAutoForward": "silent"
    }
  },
  "remoteEnv": {
    "JAVA_HOME": "/usr/lib/jvm/msopenjdk-current"
  },
  "remoteUser": "vscode",
  "features": {
    "ghcr.io/devcontainers/features/github-cli:1": {
      "installDirectlyFromGitHubRelease": true,
      "version": "latest"
    },
    "ghcr.io/devcontainers-extra/features/quarkus-sdkman:2": {
      "version": "latest",
      "jdkVersion": "none",
      "jdkDistro": "ms"
    },
    "ghcr.io/devcontainers/features/java:1": {
      "installGradle": true,
      "version": "none",
      "jdkDistro": "ms",
      "gradleVersion": "8.10.2"
    }
  }
}

🐳 Docker Compose: aplicación + base de datos

En este caso estoy utilizando Docker Compose porque mi entorno está formado por dos contenedores:

  • 🧩 Aplicación Quarkus
  • 🐘 Base de datos PostgreSQL
services:
  app:
    image: mcr.microsoft.com/devcontainers/java:21-bookworm
    volumes:
      - ..:/workspace:cached
    command: sleep infinity
    network_mode: service:db
    depends_on:
      db:
        condition: service_healthy
  db:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_USER: sports_user
      POSTGRES_PASSWORD: sports_pass
      POSTGRES_DB: sports_db
    ports:
      - "5432:5432"
      - "8080:8080"
    volumes:
      - postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U sports_user -d sports_db"]
      interval: 5s
      timeout: 5s
      retries: 5
volumes:
  postgres-data:

De esta forma consigo que todas las dependencias necesarias para el desarrollo formen parte del entorno, sin tener que instalar nada en local. En este ejemplo solo tengo una base de datos, pero el patrón escala muy bien si mañana necesitas Redis, Kafka o cualquier otro servicio.

🧩 Extensiones y settings de VS Code

Como parte del Dev Container incluyo varias extensiones clave:

  • Soporte completo para Java y Gradle
  • Extensiones específicas de Quarkus y MicroProfile
  • SQLTools para conectar directamente a PostgreSQL
  • Docker, REST Client y YAML

Además, dejo preconfiguradas algunas settings útiles, como:

  • Formateo automático al guardar
  • Organización explícita de imports
  • Conexión directa a la base de datos desde VS Code

Todo listo para empezar a trabajar nada más abrir el repositorio 🚀

🔌 Puertos y forwarding

También configuro explícitamente los puertos que necesito:

  • 8080 → Aplicación Quarkus 🚀
  • 5432 → PostgreSQL 🐘

Incluyendo etiquetas y comportamiento al auto-forward para que la experiencia sea más limpia y visual dentro de VS Code.

🧠 Por qué tuve que configurar JAVA_HOME en remoteEnv

En mi caso fue necesario definir explícitamente la variable de entorno JAVA_HOME dentro de la sección remoteEnv del Dev Container.

El motivo es que la feature quarkus-sdkman instala SDKMAN y configura automáticamente JAVA_HOME apuntando a la siguiente ruta:

/usr/local/sdkman/candidates/java/current

Sin embargo, en esta configuración no estoy instalando el JDK vía SDKMAN, ya que el JDK viene ya preinstalado en la imagen base de Microsoft (mcr.microsoft.com/devcontainers/java:21-bookworm).
Esto hace que ese symlink no exista realmente en el contenedor.

👉 Como consecuencia, herramientas como Gradle o Quarkus intentan usar un JAVA_HOME que apunta a una ruta inexistente y la ejecución falla.

La solución es sencilla: forzar JAVA_HOME a la ubicación real del JDK que trae la imagen base:

/usr/lib/jvm/msopenjdk-current

Con este override me aseguro de que todas las herramientas Java utilizan el JDK correcto y el entorno funciona de forma estable y predecible.

⚠️ Muy importante: Quarkus debe escuchar en todas las interfaces

Puede que hayas llegado hasta aquí con tu propia configuración y que, al arrancar la aplicación, te hayas encontrado con un timeout al intentar acceder desde el navegador.

Esto es muy habitual y tiene una causa clara:
👉 Quarkus, por defecto, no escucha en todas las interfaces de red.

La solución es sencilla. Solo tienes que añadir esta línea en tu application.properties:

# HTTP configuration - escuchar en todas las interfaces
quarkus.http.host=0.0.0.0

Con esto, tu aplicación será accesible correctamente desde tu máquina local, sin ensuciar tu entorno ni instalar nada en local 🎉

¡Nos vemos 👋🏻!

Deja un comentario

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.