Skip to content

Latest commit

 

History

14 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 

Repository files navigation

Vaultwarden(Containerized) on AWS via Terraform

A self-hosted password manager running in Docker on an AWS EC2 instance, provisioned end-to-end with Terraform. No clicking through the console, no manual config drift.

Built this to get hands-on with cloud networking and IaC. Everything from the VPC to the running container is defined in code.


Architecture

[ Local Arch Linux Terminal ]
         │
         ▼ (Terraform apply)
┌──────────────────────────────────────────────────────┐
│ AWS (Singapore Region) (VPC: 10.0.0.0/16)              │
│                                                      │
│  ┌─────────────────────────────────────────────────┐ │
│  │ Public Subnet (10.0.1.0/24)                     │ │
│  │                                                 │ │
│  │  ┌───────────────────────────────────────────┐  │ │
│  │  │ Security Group (Stateful Firewall)        │  │ │
│  │  │ Ingress: 80/443 (HTTP/S), 22 (SSH)        │  │ │
│  │  │                                           │  │ │
│  │  │  ┌─────────────────────────────────────┐  │  │ │
│  │  │  │ Ubuntu 24.04 EC2 Instance           │  │  │ │
│  │  │  │ Docker runtime                      │  │  │ │
│  │  │  │ Persistent volume (/vw-data/)       │  │  │ │
│  │  │  │ Vaultwarden container (TLS)         │  │  │ │
│  │  │  └─────────────────────────────────────┘  │  │ │
│  │  └───────────────────────────────────────────┘  │ │
│  └─────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘

What's in here?

Infrastructure as Code. Terraform provisions the VPC, subnet, route tables, internet gateway, security group, and EC2 instance. Run terraform apply, get a working server. No manual steps.

Automated bootstrap. A 'user_data" shell script runs on first boot to install Docker and start the container. The instance is ready to go without any SSH post-provisioning.

TLS inside the container. The ROCKET_TLS environment variable points Vaultwarden's Rocket backend at a certificate and key mounted into the container. Traffic is encrypted before it hits the application layer.

Locked-down firewall. Security group egress is restricted so the instance can pull packages and Docker images, but the inbound attack surface stays small.


Debugging log

The stuff that broke and how I fixed it.

1. File descriptor exhaustion (OS Error 24: Too Many Open Files)

What happened. Rapid browser reloads during setup — I essentially self-DDoS'd the instance — maxed out the default 1024 file descriptor limit faster than the kernel could clean up stale TCP connections.

swappy-20260604_221103

Fix. Running the container with docker run -d detaches the process from the terminal entirely. The kernel can then properly clean up half-open TCP connections instead of letting them pile up against the limit.

swappy-20260604_211719

2. Browser blocking the vault (HTTPS required)

What happened. Browsers refuse to run over plain HTTP. Bitwarden clients use this to encrypt vaults locally before sending anything to the server, so no HTTPS means no login.

Fix. I Generated a self-signed 4096-bit RSA certificate with OpenSSL directly on the instance. Then used SSH local port forwarding (-L 8080:localhost:443) to tunnel from my machine to the instance. The browser sees localhost, which satisfies the secure-context requirement without needing a public domain or a CA-signed certification.

4dac8dac-61b1-4e2a-9584-d5362ece645d

How to deploy

Prerequisites

  • Terraform CLI
  • AWS credentials configured (aws configure or environment variables)
  • An SSH key pair at ~/.ssh/portfolio_key

1. Provision the infrastructure

# Download providers and initialize the working directory
terraform init

# Preview what Terraform will create
terraform plan

# Deploy to AWS Singapore
terraform apply -auto-approve

2. Connect to the instance

# SSH in with your key
ssh -i ~/.ssh/portfolio_key ubuntu@<EC2_PUBLIC_IP>

3. Set up TLS (first time only)

# Generate a self-signed cert valid for 365 days
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes

4. Start Vaultwarden

docker run -d \
  --name vaultwarden \
  -v /vw-data/:/data/ \
  -v /home/ubuntu/cert.pem:/ssl/cert.pem \
  -v /home/ubuntu/key.pem:/ssl/key.pem \
  -e ROCKET_TLS='{certs="/ssl/cert.pem",key="/ssl/key.pem"}' \
  -p 443:443 \
  vaultwarden/server:latest

5. Tunnel and access

# From your local machine
ssh -i ~/.ssh/portfolio_key -L 8080:localhost:443 ubuntu@<EC2_PUBLIC_IP> -N

# Then open in browser
https://localhost:8080
4b0bb8bd-1b7f-40c4-991d-04603ee83d59

`


Accept the self-signed cert warning. The vault is running over an encrypted connection.

### 6. Tear down

```bash
terraform destroy -auto-approve

Tear down

Run terraform destroy when you're done. Everything in the VPC gets removed.


Tech stack

  • Terraform -- infrastructure provisioning
  • AWS -- EC2, VPC, Security Groups, Internet Gateway
  • Docker -- container runtime
  • Vaultwarden -- Bitwarden-compatible self-hosted server (Rust/Rocket)
  • OpenSSL -- self-signed TLS certificate generation
  • Local (Arch Linux) -- where the Terraform code lives. Runs terraform apply, manages the SSH key pair (ed25519), and handles the port-forwarding tunnel.
  • Cloud (Ubuntu 24.04, EC2 t3.micro) -- the Docker host. I picked Ubuntu over something leaner because I wanted a predictable base and didn't want to debug OS quirks on top of everything else. Vaultwarden runs detached with /vw-data/ mounted for persistence.

About

this is my first IaC project using Terraform, Hashicorp with AWS, documented.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages