# How to Run a HashiCorp Vault Dev Server in a Cloud Sandbox

> Install Vault from HashiCorp's apt repo, run vault server -dev under systemd, and vault kv put and kv get work at once. The dev server is in-memory and unsealed, for learning and testing only. Its secrets survived stop and resume with memory, and were gone after a restart or a cold boot.

Source: https://sandboxer.com/guides/run-vault-dev-server-in-a-sandbox. Updated 2026-10-27.

**Short answer:** Install Vault from HashiCorp's apt repo, run vault server -dev under systemd, and vault kv put and kv get work at once. The dev server is in-memory and unsealed, for learning and testing only. Its secrets survived stop and resume with memory, and were gone after a restart or a cold boot.

## At a glance

We ran everything below on production on October 27, 2026, with Vault 2.2.1 on a `small` sandbox (2 vCPU, 4 GB). For the cold boot we resized it to `default`. Each number is from a single run, and yours will differ. Every secret value on this page is fake.

| Step | Result |
| - | - |
| Install with apt | 10.3 s |
| `systemctl enable --now` to a working server | 232 ms |
| KV read over HTTP, inside the sandbox (100 reads) | median 0.3 ms |
| `vault kv get` with the CLI, inside the sandbox | 83 ms per call, mostly process start |
| KV read from a laptop through the private hosted URL (20 reads) | median 192 ms |
| Memory | about 160 MB RSS per server |
| `sbx stop --wait` then `sbx resume` (memory restored) | Dev server kept running; secrets intact |
| `sudo systemctl restart vault-dev` | All secrets gone; old tokens invalid |
| Resume on a bigger size (boots from disk) | Dev server empty; file-storage server came back **sealed**, then had its data after unseal |

## What the dev server is, and what it isn't

HashiCorp's [dev server docs](https://developer.hashicorp.com/vault/docs/concepts/dev-server) list what `vault server -dev` does. It stores all data encrypted in memory. It initializes and unseals itself. It listens on `127.0.0.1:8200` without TLS. It mounts a KV v2 engine at `secret/`. It also stores a root token so the `vault` CLI works right away. The same page calls it insecure and says it loses data on every restart.

So use it to learn Vault, to test code that reads secrets, or to give an agent a throwaway Vault. Don't put real secrets in it. To give a sandbox a real secret, see [Pass secrets to a sandbox safely](/guides/pass-secrets-to-a-sandbox-safely). This page doesn't repeat that guide.

## 1. Install Vault and run the dev server under systemd

Use HashiCorp's own apt repository ([install page](https://developer.hashicorp.com/vault/install)). Put the commands in `install.sh`:

```bash
set -e
wget -qO - https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(grep -oP '(?<=UBUNTU_CODENAME=).*' /etc/os-release || lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list >/dev/null
sudo apt-get update -qq && sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq vault >/dev/null
vault version
```

```bash
sbx new --name vault-dev --type small --no-env
id=sb_66mil9w3dd    # the id that sbx new printed

sbx scp ./install.sh $id:/home/user/
sbx exec $id --timeout 600 "bash install.sh"
```

```text
Vault v2.2.1 (0e7c41b936218bd2401ab0f891c93bec10e2e8e4), built 2026-10-22T16:27:49Z
```

`sbx exec` stops a command after 30 s by default, so keep `--timeout 600` on installs.

Run the server as a systemd unit, not a background shell command. The unit runs it as `user` with `HOME` set, so the dev server writes its root token to `/home/user/.vault-token` (mode 600). The CLI then reads it from there, and you never have to copy or print the token. Save this as `vault-dev.service`:

```ini
[Unit]
Description=Vault dev server (in-memory, for testing only)
After=network-online.target

[Service]
User=user
Environment=HOME=/home/user
ExecStart=/usr/bin/vault server -dev -dev-listen-address=127.0.0.1:8200
Restart=always

[Install]
WantedBy=multi-user.target
```

```bash
sbx scp ./vault-dev.service $id:/home/user/
sbx exec $id "sudo mv vault-dev.service /etc/systemd/system/ && sudo systemctl daemon-reload && sudo systemctl enable --now vault-dev
export VAULT_ADDR=http://127.0.0.1:8200
until vault status >/dev/null 2>&1; do sleep 0.1; done
vault status"
```

```text
Key             Value
---             -----
Seal Type       shamir
Initialized     true
Sealed          false
Total Shares    1
Threshold       1
Version         2.2.1
…
Storage Type    inmem
…
HA Enabled      false
```

`Sealed false` and `Storage Type inmem` sum up the dev server. The unit has its own name, `vault-dev`, so it won't collide with a unit for a configured server.

## 2. kv put and kv get

```bash
sbx exec $id "export VAULT_ADDR=http://127.0.0.1:8200
vault kv put secret/myapp/db username=app password=fake-password-123
vault kv get secret/myapp/db"
```

```text
==== Secret Path ====
secret/data/myapp/db

======= Metadata =======
Key                Value
---                -----
created_time       2026-10-27T10:16:01.592293466Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            1
…
====== Data ======
Key         Value
---         -----
password    fake-password-123
username    app
```

KV v2 keeps versions. After a second `put` with `password=fake-password-456`, `vault kv get -version=1 -field=password secret/myapp/db` still returned `fake-password-123`. Note the path: the CLI takes `secret/myapp/db`, and the HTTP API uses `secret/data/myapp/db`.

Reads are fast inside the sandbox. A loop of 100 `curl` reads against `/v1/secret/data/myapp/db` had a median of 0.3 ms (min 0.2 ms, max 2.5 ms). A `vault kv get` with the CLI took 83 ms per call, and nearly all of that is starting the `vault` binary. So in a hot loop, call the HTTP API or use a client library.

## 3. Give a client a narrow token

The root token can do everything. Code under test should get a token that can only read its own path. Run this `policy.sh` in the sandbox. It writes the new token to a mode-600 file and prints only its length:

```bash
umask 077
vault policy write myapp-read - <<'P'
path "secret/data/myapp/*" {
  capabilities = ["read"]
}
P
vault token create -policy=myapp-read -ttl=1h -field=token > ~/myapp-read.token
wc -c < ~/myapp-read.token
```

```text
Success! Uploaded policy: myapp-read
95
```

## 4. Reach the API from your laptop on a private URL

The dev server listens on `127.0.0.1`, so host it with `--localhost` ([hosting docs](/docs/hosting#apps-that-listen-on-localhost)):

```bash
sbx host $id 8200 --localhost --title "Vault dev"
```

```text
{"ok":true,"type":"port.hosted","sandboxId":"sb_66mil9w3dd","port":8200,"url":"https://mossy-bright-fennel-8200.box.sandboxer.com?_token=<token>","access":"private","isProtected":true,"localhost":true}
```

Copy the read-only token out with `sbx scp $id:/home/user/myapp-read.token ./read.token`. Then put it in a header file, so it stays out of your shell history and `ps`:

```bash
B=https://mossy-bright-fennel-8200.box.sandboxer.com
umask 077; printf 'X-Vault-Token: %s\n' "$(cat read.token)" > vh.txt

curl -s -o /dev/null -w "no token: %{http_code}\n" $B/v1/sys/health
curl -s -o /dev/null -w "with token: %{http_code}\n" -c jar.txt "$B/?_token=<token>"
curl -s -b jar.txt $B/v1/secret/data/myapp/db
curl -s -b jar.txt -H @vh.txt $B/v1/secret/data/myapp/db | jq -c .data.data
curl -s -b jar.txt -H @vh.txt -X POST -d '{"data":{"x":"y"}}' $B/v1/secret/data/myapp/db
```

```text
no token: 401
with token: 302
{"errors":["permission denied"]}
{"password":"fake-password-456","username":"app"}
{"errors":["1 error occurred:\n\t* permission denied\n\n"]}
```

Here two locks work in turn. Without the hosted URL's token, Sandboxer's proxy returns `401`, and Vault never sees the request. Past the proxy, Vault returns `403` until you send a Vault token. The read-only token can read but not write. `/ui/` returned `200` through the same URL, so the Vault web UI works there too. You log in to it with a Vault token. Twenty reads from our laptop took a median of 192 ms, most of it network distance to the sandbox.

Keep the URL private. A dev server has no TLS of its own and an all-powerful root token. The hosted URL adds HTTPS, but with `--public` the only lock left would be Vault's own token check.

## 5. What happens to the data on stop, restart and cold boot

We also ran a second Vault on the same sandbox with file storage (step 6), so we could compare the two.

| What happened | Dev server (`inmem`) | File storage server |
| - | - | - |
| `sbx stop $id --wait` (2.2 s), then `sbx resume $id` (1.3 s), `memoryRestored: true` | Same process (`ActiveEnterTimestamp` unchanged). Secret still there. The hosted URL and its cookie worked again | Same process, still unsealed |
| `sudo systemctl restart vault-dev` | `No value found at secret/data/myapp/db`. The read-only token from before was rejected: `permission denied`, `invalid token` | Not affected |
| `sbx resume $id --type default` (`memoryRestored: false`, `resumeReason: "resized"`) | systemd started a fresh, empty dev server with a new root token in `~/.vault-token` | systemd started it; `"sealed":true` until unsealed, then the secret was there |

A resume with memory restored keeps the dev server's data because the process never stops. You can't count on that, though. A resume boots from disk when you change the size. It also boots from disk when no machine with the same CPU type is free ([snapshots docs](/docs/snapshots)). A restart of the service wipes it too. Any time the dev server process starts again, it starts empty, and every token you handed out stops working. If your test needs its secrets, seed them in a setup script that runs after each start.

## 6. Optional: a single node with file storage

To keep data across a cold boot, run a normal server with the [filesystem storage backend](https://developer.hashicorp.com/vault/docs/configuration/storage/filesystem). HashiCorp's docs say it's officially supported and has no high availability. This is still a test setup: one node, no TLS, and here the unseal key sits on the same disk as the data. Save `config.hcl`:

```hcl
storage "file" {
  path = "/home/user/vault-file/data"
}

listener "tcp" {
  address     = "127.0.0.1:8210"
  tls_disable = true
}

api_addr      = "http://127.0.0.1:8210"
disable_mlock = true
ui            = true
```

`vault-file.service` is the dev unit with `ExecStart=/usr/bin/vault server -config=/home/user/vault-file/config.hcl`. Copy both files to the sandbox and enable the unit. Then initialize the server with one key share and write the output to a mode-600 file, not your terminal:

```bash
umask 077
mkdir -p ~/vault-file/data && mv ~/config.hcl ~/vault-file/
sudo mv ~/vault-file.service /etc/systemd/system/ && sudo systemctl daemon-reload && sudo systemctl enable --now vault-file
export VAULT_ADDR=http://127.0.0.1:8210
vault operator init -key-shares=1 -key-threshold=1 -format=json > ~/vault-file/init.json
```

Unseal with `unseal.sh`. It sends the key on stdin, so the key never appears in a command line. We tried `vault operator unseal -` first, and Vault 2.2.1 rejected it with `'key' must be a valid hex or base64 string`.

```bash
jq '{key: .unseal_keys_b64[0]}' ~/vault-file/init.json | curl -s -X PUT -d @- http://127.0.0.1:8210/v1/sys/unseal | jq -c '{sealed, progress}'
```

```bash
bash unseal.sh
export VAULT_TOKEN=$(jq -r .root_token ~/vault-file/init.json)
vault secrets enable -path=secret kv-v2
vault kv put secret/myapp/db password=fake-file-password >/dev/null
vault kv get -field=password secret/myapp/db
```

```text
{"sealed":false,"progress":0}
Success! Enabled the kv-v2 secrets engine at: secret/
fake-file-password
```

A non-dev server doesn't mount `secret/` for you, which is why `secrets enable` is there. After the cold boot in step 5, the server came up sealed. One `bash unseal.sh` brought back `fake-file-password`. For anything beyond testing, follow HashiCorp's production guidance: use [integrated storage](https://developer.hashicorp.com/vault/docs/configuration/storage/raft), TLS, and auto-unseal or key holders who aren't the server itself.

## 7. Clean up

```bash
sbx delete $id --yes
```

After the delete, `sbx info` returned `404 not_found`, and so did the hosted URL. A `small` sandbox bills at half the `default` rate of $0.036/hour while it runs, and nothing while it's stopped.

## Vault's license, and OpenBao

As of October 27, 2026, the `LICENSE` file in [hashicorp/vault](https://github.com/hashicorp/vault/blob/main/LICENSE) is the Business Source License 1.1. It covers Vault 1.15.0 and later. Its additional use grant allows production use, except offering Vault to third parties as a hosted or embedded product that competes with the licensor's paid versions. Each version converts to MPL 2.0 four years after it's published. HashiCorp [announced the switch from MPL 2.0 to BSL 1.1](https://www.hashicorp.com/en/blog/hashicorp-adopts-business-source-license) on August 10, 2023. The `LICENSE` file names IBM as the licensor today.

[OpenBao](https://openbao.org/) describes itself as an open source, community-driven fork of Vault, managed by the Linux Foundation's OpenSSF, where it is a Sandbox project. Its [GitHub repository](https://github.com/openbao/openbao) is licensed MPL-2.0. We didn't test OpenBao for this page. If you use it, check its docs for its binary name and dev mode.

## FAQ

### Is the Vault dev server safe for real secrets?

No. HashiCorp documents it as insecure: in-memory, auto-unsealed, no TLS, with a root token stored on disk for the CLI. Use fake values, and use [Pass secrets to a sandbox safely](/guides/pass-secrets-to-a-sandbox-safely) for real ones.

### Do my dev-server secrets survive an auto-stop?

Only if the resume restores memory (`memoryRestored: true`). In our tests, a restart of the service or a boot from disk started an empty server, and old tokens stopped working. If you need data across boots, use file storage (step 6) or re-seed after each start.

### Why does my old token say "invalid token"?

The dev server started again, maybe after a restart or a boot from disk. Every start makes a new root token and an empty token store. Read the new root token from `~/.vault-token` inside the sandbox and create fresh child tokens.

### Which size do I need?

`small` (2 vCPU, 4 GB) was enough. Each Vault server used about 160 MB of memory.

### Why does the hosted URL return 401 before Vault answers?

The URL is private by default. Open the full URL with `?_token=` once and it sets a cookie. Requests without it are stopped by Sandboxer's proxy and never reach Vault.

## Related

- [Pass secrets to a sandbox safely](/guides/pass-secrets-to-a-sandbox-safely)
- [Environment variables inside a sandbox](/guides/environment-variables-inside-a-sandbox)
- [Give a port in a sandbox an HTTPS URL](/guides/give-a-sandbox-port-an-https-url)
- [Pause and resume a sandbox with memory](/guides/pause-and-resume-a-sandbox-with-memory)
