Add compose demo app for VeilStream preview testing

web (node) renders the seeded customers table plus container hostname.
db (postgres:16-alpine) seeds five PII-shaped rows from db/init.sql.
This commit is contained in:
Mathew Lewis 2026-08-26 22:08:17 -07:00
commit 47fe3c33e6
7 changed files with 153 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
TOS.txt
node_modules/

21
README.md Normal file
View File

@ -0,0 +1,21 @@
# veilstream-demo
Throwaway app for testing VeilStream preview environments.
Two services:
- `web` - Node, no framework. Renders the `customers` table plus the container
hostname, so you can tell one preview environment from another.
- `db` - `postgres:16-alpine`, seeded from `db/init.sql` with five rows of
deliberately PII-shaped data (name, email, phone) to give the sanitization
features something to act on.
Primary service is `web` on port 3000. `/healthz` returns `ok`.
Run locally:
docker compose up --build
Then open
http://localhost:3000

16
db/init.sql Normal file
View File

@ -0,0 +1,16 @@
CREATE TABLE customers (
id serial PRIMARY KEY,
full_name text NOT NULL,
email text NOT NULL,
phone text NOT NULL,
city text NOT NULL,
plan text NOT NULL,
mrr_cents integer NOT NULL
);
INSERT INTO customers (full_name, email, phone, city, plan, mrr_cents) VALUES
('Ada Lovelace', 'ada@example.com', '+1 250 555 0101', 'Victoria', 'pro', 9900),
('Grace Hopper', 'grace@example.com', '+1 250 555 0102', 'Nanaimo', 'team', 29900),
('Alan Turing', 'alan@example.com', '+1 604 555 0103', 'Vancouver', 'free', 0),
('Katherine Johnson','kj@example.com', '+1 250 555 0104', 'Sidney', 'pro', 9900),
('Edsger Dijkstra', 'edsger@example.com', '+1 778 555 0105', 'Victoria', 'enterprise',99900);

26
docker-compose.yml Normal file
View File

@ -0,0 +1,26 @@
services:
web:
build:
context: ./web
ports:
- "3000:3000"
environment:
DATABASE_URL: postgres://demo:demo@db:5432/demo
APP_LABEL: "VeilStream demo"
depends_on:
db:
condition: service_healthy
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: demo
POSTGRES_PASSWORD: demo
POSTGRES_DB: demo
volumes:
- ./db/init.sql:/docker-entrypoint-initdb.d/init.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U demo -d demo"]
interval: 5s
timeout: 3s
retries: 20

7
web/Dockerfile Normal file
View File

@ -0,0 +1,7 @@
FROM node:20-alpine
WORKDIR /app
COPY package.json ./
RUN npm install --omit=dev
COPY server.js ./
EXPOSE 3000
CMD ["node", "server.js"]

9
web/package.json Normal file
View File

@ -0,0 +1,9 @@
{
"name": "veilstream-demo-web",
"version": "1.0.0",
"private": true,
"main": "server.js",
"dependencies": {
"pg": "^8.13.1"
}
}

72
web/server.js Normal file
View File

@ -0,0 +1,72 @@
const http = require("http");
const os = require("os");
const { Pool } = require("pg");
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const label = process.env.APP_LABEL || "VeilStream demo";
const port = Number(process.env.PORT || 3000);
const esc = (s) =>
String(s).replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" }[c]));
async function page() {
let rows = [];
let dbError = null;
try {
const r = await pool.query(
"SELECT id, full_name, email, phone, city, plan, mrr_cents FROM customers ORDER BY id"
);
rows = r.rows;
} catch (err) {
dbError = err.message;
}
const body = dbError
? `<p class="err">Database unreachable: ${esc(dbError)}</p>`
: `<table>
<tr><th>id</th><th>name</th><th>email</th><th>phone</th><th>city</th><th>plan</th><th>mrr</th></tr>
${rows
.map(
(r) =>
`<tr><td>${r.id}</td><td>${esc(r.full_name)}</td><td>${esc(r.email)}</td><td>${esc(
r.phone
)}</td><td>${esc(r.city)}</td><td>${esc(r.plan)}</td><td>$${(r.mrr_cents / 100).toFixed(
2
)}</td></tr>`
)
.join("\n ")}
</table>
<p class="meta">${rows.length} rows. If these values look like real people, the seed was not sanitized.</p>`;
return `<!doctype html>
<html><head><meta charset="utf-8"><title>${esc(label)}</title>
<style>
body{font:15px/1.5 -apple-system,system-ui,sans-serif;margin:2rem auto;max-width:52rem;padding:0 1rem}
table{border-collapse:collapse;width:100%;margin:1rem 0}
th,td{border:1px solid #ccc;padding:.4rem .6rem;text-align:left}
th{background:#f4f4f4}
.meta{color:#666}
.err{color:#b00}
dl{display:grid;grid-template-columns:max-content 1fr;gap:.2rem 1rem}
dt{color:#666}
</style></head><body>
<h1>${esc(label)}</h1>
<dl>
<dt>hostname</dt><dd>${esc(os.hostname())}</dd>
<dt>served at</dt><dd>${new Date().toISOString()}</dd>
<dt>node</dt><dd>${esc(process.version)}</dd>
</dl>
${body}
</body></html>`;
}
http
.createServer(async (req, res) => {
if (req.url === "/healthz") {
res.writeHead(200, { "content-type": "text/plain" });
return res.end("ok\n");
}
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
res.end(await page());
})
.listen(port, "0.0.0.0", () => console.log(`listening on ${port}`));