Saltar al contenido
ES EN
Tema: automático

Laboratorio · Self-hosting

Monta Vaultwarden con Caddy y HTTPS local en Docker Compose

Despliega Vaultwarden detrás de Caddy con versiones fijadas, registros cerrados, panel de administración con token hasheado y copias de seguridad restauradas.

Laboratorio · Self-hosting

50 min nivel medio configuración validada

Arquitectura del laboratorioComponentes: Navegador / cliente Bitwarden, Caddy, Vaultwarden, vw-data, backups.HTTPSreverse_proxy HTTPlee/escribevaultwarden backupNavegador / cliente Bitwarden: paso 3Navegador / client…https://localhostCaddy: paso 1Caddycontenedor :443, CA loc…Vaultwarden: paso 1Vaultwardencontenedor :80 internovw-data: paso 1vw-dataSQLite y adjuntosbackups: paso 4backupscopias restauradas
  1. Navegador / cliente Bitwarden · paso 3 https://localhost
  2. Caddy · paso 1 contenedor :443, CA local
  3. Vaultwarden · paso 1 contenedor :80 interno
  4. vw-data · paso 1 SQLite y adjuntos
  5. backups · paso 4 copias restauradas

Qué vas a montar y por qué

Vaultwarden es un servidor compatible con Bitwarden escrito en Rust: sirve para guardar tus contraseñas en tu propia máquina y usarlas desde los clientes oficiales de Bitwarden. En este laboratorio lo montas con Docker Compose junto a Caddy, que se encarga del HTTPS, con las imágenes fijadas a una versión exacta para que una actualización no te sorprenda.

Seguirás un orden defensivo: primero HTTPS (el panel de administración no debería activarse sin él), después registros cerrados tras crear tu cuenta, un token de administración guardado como hash argon2id y, al final, una copia de seguridad que arrancas en un contenedor temporal para comprobar que sirve. Todo vive en un único directorio y se elimina con un solo comando.

Sustituye siempre los marcadores CHANGE_ME_… por tus valores. Para uso doméstico sin dominio público, Caddy emite certificados de una CA local propia para hostnames internos como localhost; la primera vez puede pedirte una contraseña para instalar su certificado raíz en el almacén de confianza (solo ocurre una vez por certificado raíz), y los clientes que no confíen en esa CA mostrarán errores de seguridad.

Laboratorio: Monta Vaultwarden con Caddy y HTTPS local en Docker Compose

Requisitos

SistemaLinux (Debian 12 / Ubuntu 24.04) o macOS 14
Vaultwarden (imagen vaultwarden/server)1.37.4
Caddy (imagen caddy)2.11.7
Docker Compose (plugin docker compose)5.6.1
RAM2 GB
Disco5 GB

Paso a paso

  1. Crea el proyecto, el Caddyfile y el Compose con versiones fijadas

    Crea un directorio de trabajo y, antes de ejecutar nada con Compose, crea el Caddyfile: el Compose lo monta como fichero y, si no existe cuando haces docker compose up, Docker crea un directorio con ese nombre y Caddy no arranca. Por eso el Caddyfile va primero en este paso.

    Caddy sirve por HTTPS todo sitio con host definido y, para localhost, firma certificados con su propia CA local. El bloque log con roll_size y roll_keep rota el registro de acceso en el propio Caddy. header_up X-Real-IP pasa la IP real del cliente a Vaultwarden para que quede en sus registros. Las variables {$DOMAIN} y {$LOG_FILE} las lee Caddy de su entorno, definido en el servicio caddy del Compose.

    Las dos imágenes llevan una etiqueta exacta (vaultwarden/server:1.37.4 y caddy:2.11.7) en lugar de latest: así una actualización es una decisión tuya, no un efecto secundario de pull. Vaultwarden no publica ningún puerto en el host: solo Caddy lo alcanza por la red privada que Compose crea. SIGNUPS_ALLOWED arranca en true únicamente para que puedas crear tu cuenta; lo cerraremos en el paso 3. DOMAIN debe ser la URL https, porque Vaultwarden necesita saber que va sobre https para funcionar bien con los adjuntos. Si tienes un dominio propio, sustituye https://localhost en ambas variables DOMAIN por tu dominio (CHANGE_ME_DOMAIN); con un nombre DNS público Caddy usaría una CA pública, lo que exige puertos 80 y 443 accesibles desde fuera. El puerto 80 se publica para la redirección de HTTP a HTTPS, que Caddy aplica por defecto.

    mkdir -p ~/vaultwarden && cd ~/vaultwarden
    mkdir -p vw-data caddy-config caddy-data backups

    Fichero Caddyfile

    {$DOMAIN} {
    	log {
    		level INFO
    		output file {$LOG_FILE} {
    			roll_size 10MB
    			roll_keep 10
    		}
    	}
    
    	encode zstd gzip
    
    	reverse_proxy vaultwarden:80 {
    		header_up X-Real-IP {remote_host}
    	}
    }

    Fichero compose.yaml

    services:
      vaultwarden:
        image: vaultwarden/server:1.37.4
        container_name: vaultwarden
        restart: always
        environment:
          DOMAIN: "https://localhost"
          SIGNUPS_ALLOWED: "true"
        volumes:
          - ./vw-data:/data
    
      caddy:
        image: caddy:2.11.7
        container_name: caddy
        restart: always
        ports:
          - 80:80
          - 443:443
          - 443:443/udp
        volumes:
          - ./Caddyfile:/etc/caddy/Caddyfile:ro
          - ./caddy-config:/config
          - ./caddy-data:/data
        environment:
          DOMAIN: "https://localhost"
          LOG_FILE: "/data/access.log"
  2. Arranca los contenedores

    Con el Caddyfile y el compose.yaml ya en el directorio, levanta el proyecto. Compose crea la red privada, descarga las dos imágenes con la etiqueta fijada y arranca ambos servicios. Revisa los registros de Caddy para confirmar que no hay errores de configuración.

    docker compose up -d
    docker compose logs --tail 20 caddy
  3. Crea tu cuenta, cierra los registros y protege el panel de administración

    Abre https://localhost, acepta el aviso del certificado local (o instala la CA de Caddy como de confianza) y crea tu cuenta con una contraseña maestra larga. Después cierra los registros: en el Compose siguiente SIGNUPS_ALLOWED pasa a "false", para que nadie más pueda registrarse.

    El panel /admin solo se activa si defines ADMIN_TOKEN. Se recomienda activar HTTPS antes (ya lo tienes) y guardar el token como hash argon2id (cadena PHC) en lugar de texto plano. Genera el hash con el comando integrado vaultwarden hash: te pide la contraseña dos veces e imprime el hash. La contraseña con la que lo creas será la que uses para entrar en /admin; la sesión del panel dura 20 minutos por defecto (ADMIN_SESSION_LIFETIME la modifica, pero no la alargues sin necesidad).

    Al poner el hash en environment, según la documentación de Vaultwarden hay que escapar cada $ del hash duplicándolo como $$; si no, Compose intenta interpolarlo. Escribe el hash directamente en el Compose con los $$, reemplazando CHANGE_ME_ARGON2_PHC_STRING por el hash generado con cada $ duplicado. Después aplica los cambios recreando el contenedor: sin este paso los registros seguirían abiertos y el panel desactivado.

    docker run --rm -it vaultwarden/server:1.37.4 /vaultwarden hash

    Fichero compose.yaml

    services:
      vaultwarden:
        image: vaultwarden/server:1.37.4
        container_name: vaultwarden
        restart: always
        environment:
          DOMAIN: "https://localhost"
          SIGNUPS_ALLOWED: "false"
          ADMIN_TOKEN: CHANGE_ME_ARGON2_PHC_STRING
        volumes:
          - ./vw-data:/data
    
      caddy:
        image: caddy:2.11.7
        container_name: caddy
        restart: always
        ports:
          - 80:80
          - 443:443
          - 443:443/udp
        volumes:
          - ./Caddyfile:/etc/caddy/Caddyfile:ro
          - ./caddy-config:/config
          - ./caddy-data:/data
        environment:
          DOMAIN: "https://localhost"
          LOG_FILE: "/data/access.log"

    Sustituye cada valor CHANGE_ME_… por uno tuyo antes de arrancar: no dejes contraseñas ni claves de ejemplo.

    docker compose up -d
  4. Haz una copia de seguridad y restáurala para comprobarla

    Los datos viven en ./vw-data: la base SQLite (db.sqlite3), los adjuntos (attachments), config.json si has usado el panel, los ficheros rsa_key* y, opcionalmente, sends. No copies la base en uso con cp: la documentación recomienda el respaldo de SQLite por su API de copia en línea. Desde la versión 1.32.1 hay un comando integrado, vaultwarden backup, que evita instalar sqlite3 (la imagen no lo incluye). El comando deja un fichero de respaldo de SQLite en la carpeta de datos.

    Copia a ./backups ese fichero de respaldo y los ficheros rsa_key*. Los directorios attachments y sends y el fichero config.json pueden no existir: según la documentación, attachments no está presente si nunca se creó un adjunto y config.json solo existe si se ha configurado desde el panel. Por eso se copian aparte y con una comprobación previa: omite los que no existan. Si cifras la copia, hazlo sobre todo por config.json, que puede contener el token de administración y credenciales SMTP en claro.

    Una copia que nunca has restaurado no es una copia. Para comprobarla, monta la copia como carpeta de datos de un contenedor temporal de Vaultwarden sin Caddy (Vaultwarden usa por defecto db.sqlite3, así que renombra el respaldo a ese nombre en la carpeta de prueba). El contenedor se lanza en segundo plano con -d; lee sus registros con docker logs y, al terminar, deténlo con docker stop (como se creó con --rm, se elimina solo).

    docker exec -it vaultwarden /vaultwarden backup
    ls -l vw-data
    mkdir -p backups/vw-copia
    cp vw-data/db_*.sqlite3 backups/vw-copia/
    cp -a vw-data/rsa_key* backups/vw-copia/
    # attachments, sends y config.json pueden no existir: copia solo los que veas en el listado
    [ -d vw-data/attachments ] && cp -a vw-data/attachments backups/vw-copia/
    [ -d vw-data/sends ] && cp -a vw-data/sends backups/vw-copia/
    [ -f vw-data/config.json ] && cp -a vw-data/config.json backups/vw-copia/
    ls -l backups/vw-copia

    Salida esperada (ilustrativa: el formato exacto puede variar)

    -rw-r--r-- 1 root root  ... db.sqlite3
    -rw-r--r-- 1 root root  ... db_<marca-de-tiempo>.sqlite3
    drwxr-xr-x 2 root root  ... icon_cache
    -rw------- 1 root root  ... rsa_key.pem

    Salida esperada (ilustrativa: el formato exacto puede variar)

    -rw-r--r-- 1 root root  ... db_<marca-de-tiempo>.sqlite3
    -rw------- 1 root root  ... rsa_key.pem
    mkdir -p backups/prueba-restauracion
    cp -a backups/vw-copia/. backups/prueba-restauracion/
    cp backups/prueba-restauracion/db_*.sqlite3 backups/prueba-restauracion/db.sqlite3
    docker run -d --rm --name vw-restore-test -v "$(pwd)/backups/prueba-restauracion:/data" vaultwarden/server:1.37.4
    docker logs vw-restore-test
    docker stop vw-restore-test

Comprueba que funciona

  1. Comprueba que HTTPS responde

    Pide la cabecera de la web por HTTPS. Usa --insecure solo si aún no has confiado en la CA local de Caddy; con la CA instalada puedes omitirlo.

    curl --insecure --head https://localhost

    Salida esperada (ilustrativa: el formato exacto puede variar)

    HTTP/2 200
  2. Comprueba que los registros están cerrados y que el cliente funciona

    Abre la web de Vaultwarden e intenta crear otra cuenta: no debería permitírtelo. Después, en el cliente (extensión de navegador o aplicación), elige «Autoalojado» y escribe https://localhost como URL del servidor. Inicia sesión con tu cuenta y guarda un elemento de prueba. Si el cliente no acepta el certificado, instala la CA raíz de Caddy como de confianza en ese dispositivo.

  3. Verifica la copia de seguridad

    Comprueba que el respaldo con marca de tiempo existe y no está vacío, y que el contenedor temporal del paso 4 arrancó sin errores en los registros que mostró docker logs. Revisa también los registros del servicio principal.

    ls -l backups/vw-copia/db_*.sqlite3
    docker compose logs --tail 20 vaultwarden
Si algo falla

El navegador o el cliente muestra un error de certificado.

Es lo esperado con una CA local: solo es de confianza donde esté instalado el certificado raíz de Caddy, que se guarda en su directorio de datos en pki/authorities/local. En este laboratorio ese directorio está montado en ./caddy-data, así que busca allí el root.crt (la ruta exacta puede incluir un subdirectorio intermedio) e instálalo como de confianza en cada dispositivo, o usa un dominio propio con certificado de una CA pública.

find caddy-data -name root.crt

Compose avisa de que las variables argon2id, v o m no están definidas y el panel rechaza tu contraseña.

El hash contiene símbolos $ que Compose intenta interpolar. Con el hash escrito en environment, cada $ debe duplicarse como $$, como documenta Vaultwarden.

Caddy no arranca porque los puertos 80 o 443 están ocupados, o aparece un directorio llamado Caddyfile.

Si los puertos están ocupados, otro servicio del host los usa: detenlo o cambia el mapeo de puertos de Caddy. Si cambias el puerto externo, la variable DOMAIN debe llevar el puerto (por ejemplo https://localhost:8443) en ambos servicios, y los clientes deben usar esa URL. Si Docker creó un directorio Caddyfile porque ejecutaste up antes de crear el fichero, bórralo, crea el fichero del paso 1 y vuelve a arrancar.

docker compose logs --tail 30 caddy

Los adjuntos no se descargan bien en Firefox.

El propio ejemplo de la wiki de Vaultwarden avisa de que encode zstd gzip puede dar problemas de compatibilidad con algunos navegadores y sugiere desactivarlo. Comenta esa línea del Caddyfile y vuelve a crear el contenedor de Caddy con docker compose up -d --force-recreate caddy.

Endurece la instalación

Cierra el panel de administración cuando no lo uses

Para deshabilitarlo, elimina ADMIN_TOKEN del Compose (y no definas DISABLE_ADMIN_TOKEN), borra también la clave admin_token de vw-data/config.json si ese fichero existe, y recrea el contenedor. Si sospechas que alguien tiene una sesión válida del panel, borra rsa_key.pem de la carpeta de datos y reinicia Vaultwarden: se regenera la clave y las sesiones quedan invalidadas.

Mantén las versiones fijadas y actualiza con intención

Para actualizar, cambia la etiqueta en compose.yaml, lee las notas de la versión (la 1.37.4 incluye correcciones de seguridad) y ejecuta docker compose pull seguido de docker compose up -d. Haz antes una copia y restáurala como en el paso 4. Usa una versión reciente del plugin Compose (aquí la 5.6.1, la última que muestran las notas de versión); el laboratorio solo emplea comandos básicos de Compose.

docker compose pull
docker compose up -d

Guarda copias fuera del equipo y cifra lo sensible

Una copia en el mismo disco no te protege de un fallo del disco. Duplícala en otro equipo y cifra al menos config.json, que puede contener el token de administración y credenciales SMTP en texto claro.

Limpieza: deshacer el laboratorio

Elimina el laboratorio

Antes de borrar nada, copia backups a otro lugar si quieres conservar tus datos: viven dentro del directorio del proyecto y se perderán con él. Después detén y borra los contenedores y la red de Compose, y por último el directorio del proyecto. También puedes retirar del almacén de confianza de tus dispositivos el certificado «Caddy Local Authority».

cd ~/vaultwarden
docker compose down
cd ..
rm -rf vaultwarden

Qué está verificado

  • Caddyfile: sin validador automático, revísalo a mano
  • Sintaxis de compose.yaml (parser YAML y estructura de Compose)
  • Sintaxis de compose.yaml (parser YAML y estructura de Compose)
  • Sintaxis de 12 bloque(s) de comandos (bash -n en un contenedor aislado, sin ejecutarlos)
  • Versiones (3), opciones (5) y claves de configuración (11) contrastadas con 19 páginas de documentación oficial
  • Arranque real del laboratorio: no ejecutado

Comprobaciones automáticas del 28 de octubre de 2026. Lo marcado con «·» no se ha ejecutado: revísalo en tu entorno.

Fuentes

Artículo generado mediante AI.larebelion

Kernel

· Dirección de tecnología · España

«Un gestor de contraseñas en casa vale lo que valga la última copia que hayas restaurado de verdad.»