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
Pulsa una pieza para ver su paso.
- Navegador / cliente Bitwarden · paso 3 https://localhost
- Caddy · paso 1 contenedor :443, CA local
- Vaultwarden · paso 1 contenedor :80 interno
- vw-data · paso 1 SQLite y adjuntos
- 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.

Requisitos
Paso a paso
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 hacesdocker 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 bloquelogconroll_sizeyroll_keeprota el registro de acceso en el propio Caddy.header_up X-Real-IPpasa 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 serviciocaddydel Compose.Las dos imágenes llevan una etiqueta exacta (
vaultwarden/server:1.37.4ycaddy:2.11.7) en lugar delatest: así una actualización es una decisión tuya, no un efecto secundario depull. Vaultwarden no publica ningún puerto en el host: solo Caddy lo alcanza por la red privada que Compose crea.SIGNUPS_ALLOWEDarranca entrueúnicamente para que puedas crear tu cuenta; lo cerraremos en el paso 3.DOMAINdebe ser la URL https, porque Vaultwarden necesita saber que va sobre https para funcionar bien con los adjuntos. Si tienes un dominio propio, sustituyehttps://localhosten ambas variablesDOMAINpor 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 backupsFichero
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.yamlservices: 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"Arranca los contenedores
Con el
Caddyfiley elcompose.yamlya 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 caddyCrea 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 siguienteSIGNUPS_ALLOWEDpasa a"false", para que nadie más pueda registrarse.El panel
/adminsolo se activa si definesADMIN_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 integradovaultwarden 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_LIFETIMEla 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$$, reemplazandoCHANGE_ME_ARGON2_PHC_STRINGpor 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 hashFichero
compose.yamlservices: 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 -dHaz una copia de seguridad y restáurala para comprobarla
Los datos viven en
./vw-data: la base SQLite (db.sqlite3), los adjuntos (attachments),config.jsonsi has usado el panel, los ficherosrsa_key*y, opcionalmente,sends. No copies la base en uso concp: 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 instalarsqlite3(la imagen no lo incluye). El comando deja un fichero de respaldo de SQLite en la carpeta de datos.Copia a
./backupsese fichero de respaldo y los ficherosrsa_key*. Los directoriosattachmentsysendsy el ficheroconfig.jsonpueden no existir: según la documentación,attachmentsno está presente si nunca se creó un adjunto yconfig.jsonsolo 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 porconfig.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 condocker logsy, al terminar, deténlo condocker 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-copiaSalida 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.pemSalida esperada (ilustrativa: el formato exacto puede variar)
-rw-r--r-- 1 root root ... db_<marca-de-tiempo>.sqlite3 -rw------- 1 root root ... rsa_key.pemmkdir -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
Comprueba que HTTPS responde
Pide la cabecera de la web por HTTPS. Usa
--insecuresolo si aún no has confiado en la CA local de Caddy; con la CA instalada puedes omitirlo.curl --insecure --head https://localhostSalida esperada (ilustrativa: el formato exacto puede variar)
HTTP/2 200Comprueba 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://localhostcomo 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.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.crtCompose 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 caddyLos 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 -dGuarda 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 vaultwardenQué 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
- Vaultwarden: Using Docker Compose github.com
- Vaultwarden: Enabling admin page github.com
- Vaultwarden: Backing up your vault github.com
- Caddy: Automatic HTTPS caddyserver.com
- Vaultwarden: Releases github.com
¿Te ha funcionado este laboratorio?
Puntúalo de 1 a 5 estrellas. Un voto por persona; puedes cambiarlo cuando quieras.
Artículo generado mediante AI.larebelion

