Gestor de contraseñas: Vaultwarden en Docker con HTTPS

Un único contenedor, HTTPS, acceso directo por IP

Guía técnica para levantar Vaultwarden (servidor compatible con Bitwarden) en Docker, en un solo contenedor con HTTPS propio (certificado autofirmado) servido por el propio Vaultwarden. Todo pensado para copiar y pegar tal cual.

1. Introducción

Vaultwarden es una reimplementación en Rust, ligera, de la API de servidor de Bitwarden. Permite usar los clientes oficiales de Bitwarden (navegador, extensión, móvil, escritorio) contra un servidor propio, autoalojado, sin depender de la nube de Bitwarden. Esta guía lo levanta en un único contenedor Docker, con HTTPS servido por el propio Vaultwarden.

El vault web de Vaultwarden, igual que le pasa a otros clientes web modernos, exige que la página se cargue en un contexto seguro: HTTPS, o localhost. Una IP normal de la red servida por http no lo cumple, y sin ello el navegador deshabilita el cifrado que el propio vault necesita para funcionar. La solución más simple aquí no es un proxy aparte: Vaultwarden puede servir HTTPS él mismo con un certificado autofirmado, montado como volumen y activado con una única variable (ROCKET_TLS). Un solo contenedor, sin piezas adicionales.

2. Variables de esta guía

Todos los bloques usan el siguiente marcador en MAYÚSCULAS. Sustitúyelo por el valor real de tu servidor.

MarcadorQué esDónde se obtiene
IP_HOST_DOCKERIP del servidor donde corre DockerLa interfaz de red de ese servidor (ip a)
IP_HOST_DOCKER es también el texto que se escribe como CN y subjectAltName al generar el certificado (sección 6). Si esa IP cambia más adelante, hay que regenerar el certificado.
Si en el mismo servidor ya tienes otro servicio publicando el puerto 8444, cámbialo por otro libre (por ejemplo 8445) en las secciones 7, 8, 10, 11, 12 y 13 antes de arrancar. Dos contenedores no pueden publicar el mismo puerto del host a la vez, aunque cada uno tenga su propio certificado.

3. Arquitectura y flujo

Un único contenedor recibe todo el tráfico HTTPS directamente, sin proxy delante: el propio Vaultwarden termina el TLS con el certificado autofirmado montado como volumen.

Resumen visual
[Navegador, en cualquier equipo de la red]
      |
      |  https://IP_HOST_DOCKER:8444  (certificado autofirmado)
      v
[vaultwarden] :8444  (HTTPS servido por el propio contenedor)
      |
      v
 ./vw-data  (volumen persistente: bóveda, config, adjuntos)
 ./certs    (volumen: cert.pem + key.pem)
El acceso es directo por IP dentro de la red local; nada se expone a internet salvo que se configure expresamente para ello.

4. Requisitos

  • Un servidor con Ubuntu Server, físico o virtual, con red en la misma LAN desde la que vas a acceder.
  • Docker Engine y el plugin Docker Compose (docker compose, sin guion) instalados en ese servidor.
  • No hace falta salida a internet: todo el flujo vive dentro de la red local.
  • Un navegador cualquiera en otro equipo de la misma red (o en el propio servidor).
  • Confirmar que el puerto 8444 está libre en ese servidor (ss -tlnp | grep 8444); si ya lo usa otro contenedor, elige otro puerto antes de empezar.

5. Crear la carpeta del stack

Crear /docker/vaultwarden
sudo mkdir -p /docker/vaultwarden && cd /docker/vaultwarden
sudo mkdir -p vw-data certs

6. Generar el certificado autofirmado

Certificado con clave EC (curva elíptica), válido para la IP del servidor:

Generar cert.pem y key.pem
cd /docker/vaultwarden
sudo openssl ecparam -name prime256v1 -genkey -noout -out certs/key.pem
sudo openssl req -new -x509 -key certs/key.pem -out certs/cert.pem -days 825 \
  -subj "/CN=IP_HOST_DOCKER" \
  -addext "subjectAltName=IP:IP_HOST_DOCKER"

7. Crear el .env

Crear el archivo
sudo nano .env

Pega esto, sustituyendo IP_HOST_DOCKER y dejando ADMIN_TOKEN pendiente hasta la sección 9:

.env
# URL de acceso: IP del servidor + puerto HTTPS
DOMAIN=https://IP_HOST_DOCKER:8444

# Se rellena en la sección 9, con el hash generado por "vaultwarden hash"
ADMIN_TOKEN=PENDIENTE_SECCION_9

# Registro abierto mientras se hace la prueba; se cierra en la sección 11
SIGNUPS_ALLOWED=true

# HTTPS servido por el propio Vaultwarden
ROCKET_TLS={certs="/ssl/cert.pem",key="/ssl/key.pem"}
ROCKET_PORT=8444
Las rutas /ssl/cert.pem y /ssl/key.pem son rutas dentro del contenedor, no del host: el volumen ./certs:/ssl:ro del docker-compose.yml (sección 8) es el que hace que coincidan con los ficheros generados en la sección 6.
El ADMIN_TOKEN se carga aquí con env_file: en vez de con environment: ${ADMIN_TOKEN} en el docker-compose.yml, y es a propósito: el hash de la sección 9 lleva varios símbolos $ dentro ($argon2id$v=19$...), y con env_file: Docker Compose los pasa tal cual al contenedor, sin intentar interpretarlos como sus propias variables. Si alguna vez migras esto a environment:, tendrías que duplicar cada $ como $$.

8. Crear el docker-compose.yml

Crear el archivo
sudo nano docker-compose.yml
docker-compose.yml
services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    env_file:
      - .env
    volumes:
      - ./vw-data:/data
      - ./certs:/ssl:ro
    ports:
      - "8444:8444"
Un único servicio, sin proxy: el puerto 8444 se publica directamente desde el contenedor de Vaultwarden, que ya sirve HTTPS gracias a ROCKET_TLS (sección 7).

9. Generar el ADMIN_TOKEN

El panel /admin exige un token hasheado con Argon2, no una contraseña en texto plano:

Generar el hash
docker run --rm -it vaultwarden/server:latest /vaultwarden hash

Introduce la contraseña que quieras usar para /admin. Copia el hash completo (empieza por $argon2id$...) y sustitúyelo en .env:

Editar .env
sudo nano .env
# Sustituye la línea:
# ADMIN_TOKEN=PENDIENTE_SECCION_9
# por:
# ADMIN_TOKEN=$argon2id$v=19$m=...(hash completo)...
Guarda ese hash completo entre las mismas líneas del .env, sin comillas adicionales. La contraseña en claro no se guarda en ningún sitio: solo tú la recuerdas para entrar en /admin.

10. Arrancar el stack y verificar

Levantar el contenedor
cd /docker/vaultwarden
docker compose up -d
docker compose ps
Ver logs
docker logs -f vaultwarden

Busca una línea de arranque sin errores de certificado ni de permisos. Ctrl+C para salir del log (el contenedor sigue corriendo).

Probar desde el propio host
curl -kv https://IP_HOST_DOCKER:8444/alive
Si devuelve una fecha/hora, Vaultwarden está sirviendo correctamente por HTTPS.

11. Crear la cuenta y cerrar el registro

Desde el navegador, en https://IP_HOST_DOCKER:8444, pulsa Crear cuenta y registra tu email y contraseña maestra. Inicia sesión y confirma que la bóveda carga.

Entra en el panel de administración:

URL del panel admin
https://IP_HOST_DOCKER:8444/admin

Usa la contraseña en claro de la sección 9 (no el hash). Una vez creada tu cuenta, cierra el registro público:

Cerrar SIGNUPS_ALLOWED
sudo nano .env
# Cambia:
# SIGNUPS_ALLOWED=true
# por:
# SIGNUPS_ALLOWED=false

docker compose up -d

12. Acceder desde el navegador

URL de acceso
https://IP_HOST_DOCKER:8444
El navegador mostrará un aviso de conexión no privada o certificado no confiable. Es esperado: el certificado es autofirmado. Pulsa "Avanzado" y "Continuar a IP_HOST_DOCKER (no seguro)" (el texto exacto varía según el navegador) para seguir.
Tras aceptar el aviso, debería cargar el login de Vaultwarden sin errores de "navegador no compatible". Inicia sesión, crea una entrada de prueba en la bóveda y confirma que se guarda correctamente.

13. Tabla de puertos y componentes

PuertoComponenteSentido de la conexiónPara qué sirve
8444/tcpvaultwarden (Docker)Navegador → contenedorHTTPS servido por el propio Vaultwarden (certificado autofirmado, sin proxy)
./vw-data (volumen)Contenedor ↔ hostPersistencia de la bóveda, adjuntos y configuración
./certs (volumen)Host → contenedor (solo lectura)Certificado y clave usados por ROCKET_TLS

14. Errores comunes

ProblemaCausa probableSolución
El contenedor no arranca, error de port is already allocated Otro contenedor en el mismo servidor ya está publicando el puerto 8444 (o el que hayas elegido) docker ps para localizar qué lo está usando; cambiar el puerto en las secciones 7, 8, 10, 11 y 12, o parar el otro servicio
El contenedor no arranca, error relacionado con ROCKET_TLS Ruta incorrecta en el valor de ROCKET_TLS, o los ficheros cert.pem/key.pem no están en ./certs Revisar que ./certs contiene ambos ficheros (sección 6) y que la ruta en .env es exactamente /ssl/cert.pem y /ssl/key.pem (sección 7)
El navegador o curl -k dan un error de protocolo TLS raro (no el aviso normal de certificado no confiable, sino algo tipo alert internal error o conexión que se corta a medias) Puede ser un problema del propio sistema o del contenedor, no del certificado en sí Aislarlo probando el mismo cert.pem/key.pem directamente con openssl s_server -key certs/key.pem -cert certs/cert.pem -accept 9999 -www fuera de Docker; si eso también falla, el problema es del sistema, no de Vaultwarden
/admin da "no autorizado" Se está usando el hash guardado en .env en vez de la contraseña en claro Usar la contraseña que escribiste al ejecutar vaultwarden hash (sección 9), no el hash resultante
Cambios en .env no se aplican Falta relanzar el contenedor tras editar el archivo docker compose up -d de nuevo (Vaultwarden no recarga en caliente)

15. Notas de ampliación

Este despliegue cubre lo esencial: un contenedor, HTTPS propio y una cuenta de usuario. Para un uso más allá de un entorno de pruebas, conviene ampliar con:

  • Importar el certificado en el almacén de confianza de cada equipo cliente, para que el aviso de "certificado no confiable" desaparezca de forma permanente.
  • Dominio propio + Let's Encrypt en vez de certificado autofirmado, si el servicio va a estar accesible desde fuera de la red local.
  • SSO corporativo (Entra ID, Google Workspace u otro proveedor SAML/OIDC), para centralizar el inicio de sesión con las cuentas ya existentes en la organización.
  • Apps cliente (extensión de navegador, móvil, escritorio de Bitwarden) apuntando al mismo DOMAIN, una vez el certificado esté importado o sea válido.
  • Copias de seguridad de ./vw-data (incluye la bóveda cifrada y la configuración del servidor).