9.8 KiB
🔐 PasteVault
A secure, self-hosted code and text sharing tool with syntax highlighting, expiring links, and optional password protection. Think Pastebin — but private, expiring, and deployed on Azure.
What the Application Does
PasteVault allows users to:
- Paste code or text and receive a unique shareable URL
- Select a programming language for syntax highlighting (15 languages supported)
- Set an expiry time (1 hour, 1 day, 7 days, 30 days, or never)
- Optionally password-protect a paste
- Share the link — only people with the URL (and password if set) can view it
- Copy paste content or the share URL with one click
- View metadata: creation time, expiry countdown, view count
Pastes are automatically deleted from the database when they expire.
Architecture Overview
The application uses a hybrid cloud architecture:
- Azure VM (
Standard_D2s_v3— 2 vCPU, 8 GB RAM, West Europe) runs all application containers via Docker Compose - Azure PostgreSQL Flexible Server (
Standard_B1MS, North Europe) stores all paste data persistently - Azure Blob Storage (North Europe) stores database backup dumps
- Cloudflare (external, pre-existing) provides DNS, HTTPS/TLS termination, and access logs
Containers (3 total)
| Container | Image | Role |
|---|---|---|
pastevault-frontend |
Custom (Nginx + React) | Serves the UI and proxies /api/ to backend |
pastevault-backend |
Custom (Python FastAPI) | REST API — paste CRUD, expiry logic |
pastevault-redis |
redis:7-alpine |
Caching paste reads, reducing DB load |
Communication Flow
Browser → Cloudflare (HTTPS) → VM port 80 → Nginx (frontend container)
↓ /api/* proxy
FastAPI (backend container)
↓
Redis (cache, 30s TTL)
↓ cache miss
Azure PostgreSQL (persistent store)
Cloud Services Used
| Service | Purpose | Tier |
|---|---|---|
Azure VM Standard_D2s_v3 |
Runs Docker Compose with all 3 containers | Pay-as-you-go |
| Azure PostgreSQL Flexible Server | Managed relational database | Burstable B1MS (free tier) |
| Azure Blob Storage | Backup storage for pg_dump files |
Standard LRS |
| Cloudflare (pre-existing) | DNS, HTTPS certificate, access logs | Free plan |
Persistent Storage
- PostgreSQL stores all paste records (id, title, content, language, expiry, views, password hash)
- Redis Docker volume (
redis_data) persists cache data across restarts using--appendonly yes - Azure Blob Storage holds scheduled database backups
Auto-restart
All three containers use restart: always in docker-compose.yml. If any container crashes, Docker restarts it automatically without any manual intervention.
File Structure
sk1/
├── backend/
│ ├── main.py # FastAPI app — all API endpoints, DB logic, Redis caching
│ ├── requirements.txt # Python dependencies
│ └── Dockerfile # Python 3.12 slim image
├── frontend/
│ ├── src/
│ │ ├── main.jsx # React entry point
│ │ ├── App.jsx # Router — two routes: / and /paste/:id
│ │ ├── CreatePaste.jsx # Paste creation form
│ │ ├── ViewPaste.jsx # Paste viewer with syntax highlighting
│ │ └── index.css # Dark theme stylesheet
│ ├── index.html # HTML shell
│ ├── package.json # Node dependencies (React, Vite, highlight.js)
│ ├── vite.config.js # Vite build config with API proxy for local dev
│ ├── nginx.conf # Nginx config — serves React, proxies /api/ to backend
│ └── Dockerfile # Multi-stage: Node build → Nginx serve
├── scripts/
│ ├── prepare-app.sh # Full Azure provisioning + deployment script
│ ├── remove-app.sh # Teardown script — stops containers + deletes all Azure resources
│ └── backup.sh # pg_dump → Azure Blob Storage
├── docker-compose.yml # Defines frontend, backend, redis services
├── .env.example # Template for environment variables (safe to commit)
├── .gitignore # Excludes .env, node_modules, __pycache__, dist
└── README.md # This file
Configuration
All configuration is done via environment variables in the .env file. This file is never committed to Git.
| Variable | Description |
|---|---|
DATABASE_URL |
PostgreSQL connection string (without ?ssl=) |
REDIS_URL |
Redis connection string (default: redis://redis:6379) |
SECRET_KEY |
Used to salt password hashes |
DB_PASS |
PostgreSQL admin password (used by backup script) |
Copy .env.example to .env and fill in your values before running any scripts.
How to Run the Application
Prerequisites
- Azure CLI installed and logged in (
az login) - SSH key at
~/.ssh/id_rsa .envfile filled in
Deploy
cd sk1/
bash scripts/prepare-app.sh
This script automatically:
- Creates the Azure resource group
- Provisions the VM (Standard_D2s_v3, West Europe)
- Creates PostgreSQL Flexible Server (B1MS, North Europe)
- Creates Azure Blob Storage for backups
- Installs Docker on the VM
- Copies app files to the VM
- Builds and starts all containers
Teardown
bash scripts/remove-app.sh
Stops all containers and deletes the entire Azure resource group (VM, database, storage, IP).
Conditions to run scripts
prepare-app.sh: Azure CLI logged in,.envpresent, SSH key at~/.ssh/id_rsa, no existingpastevault-rgresource groupremove-app.sh: Azure CLI logged in, SSH access to VM still working
Using the Application
- Open the app URL in a browser (
https://yourdomain.com) - Enter a title, paste your code or text into the editor
- Select the language for syntax highlighting
- Choose an expiry time (or never)
- Optionally set a password
- Click "🔒 Create Paste"
- You are redirected to the paste URL — copy it with the "🔗 Share" button
- Anyone with the link can view the paste (and password if set)
Backup
Run from the project root on your local machine (requires postgresql-client):
bash scripts/backup.sh
This dumps the entire pastevault database with pg_dump and uploads it to Azure Blob Storage.
List all backups:
az storage blob list \
--account-name pastevaultstorage \
--container-name backups \
--output table
Download a specific backup:
az storage blob download \
--account-name pastevaultstorage \
--container-name backups \
--name pastevault_backup_YYYYMMDD_HHMMSS.sql \
--file ./restore.sql
Restore from backup:
PGPASSWORD=yourpassword psql \
--host=pastevault-db.postgres.database.azure.com \
--username=pvadmin \
--dbname=pastevault \
--file=./restore.sql \
--sslmode=require
Viewing Access Logs
From Cloudflare (internet traffic)
Cloudflare dashboard → your domain → Analytics tab shows:
- Total requests, unique visitors
- Geographic breakdown
- Traffic over time
- Blocked threats
From Nginx (per-request logs)
ssh azureuser@YOUR_VM_IP
docker logs pastevault-frontend
Each line shows: IP address, timestamp, HTTP method, path, status code, response size.
From the backend
docker logs pastevault-backend
Live log stream
docker logs -f pastevault-frontend
Cost Analysis — 1000 Users/Day, 50 GB Data
| Resource | Spec | Billing | Monthly | Annual |
|---|---|---|---|---|
Azure VM Standard_D2s_v3 |
2 vCPU, 8 GB, West Europe | Per hour | ~$7.30 | ~$87.60 |
| Azure PostgreSQL B1MS | 1 vCPU, 2 GB, 50 GB storage | Per hour + GB | ~$13.80 | ~$165.60 |
| Azure Blob Storage | 50 GB backups, LRS | Per GB/month | ~$1.00 | ~$12.00 |
| Azure Public IP | Standard static IP | Per hour | ~$3.00 | ~$36.00 |
| Cloudflare | DNS + HTTPS + CDN | — | $0 | $0 |
| Total | ~$25.10 | ~$301.20 |
At 1000 users/day the PostgreSQL B1MS handles load comfortably. For 10,000+ users/day, scaling to Standard_D2s_v3 for the DB (~$75/month) would be recommended.
The biggest cost saving in this architecture is using Cloudflare as the HTTPS/CDN layer for free instead of an Azure Application Gateway (~$120/month).
Known Issues Encountered During Deployment
- Azure B-series VMs unavailable in West Europe and North Europe — at time of deployment,
Standard_B1s,Standard_B2s, andStandard_B2ats_v2had capacity restrictions across multiple European regions. Resolved by usingStandard_D2s_v3in West Europe. - PostgreSQL SSL configuration conflict —
asyncpgdoes not accept?ssl=requirein the connection URL when also passed as a keyword argument. Resolved by keepingssl=Truein code only and removing it from theDATABASE_URL. - Docker Compose
versionfield warning — Docker Compose v2 treats theversionkey as obsolete. Harmless warning, does not affect functionality.
External Resources
| Resource | Type | Usage |
|---|---|---|
| FastAPI documentation | Official docs | API structure, startup events, dependency injection |
| asyncpg documentation | Official docs | PostgreSQL async connection pool configuration |
| highlight.js | Open source library | Client-side syntax highlighting for 15 languages |
| Azure CLI documentation | Official docs | VM, PostgreSQL, and storage provisioning commands |
| Docker documentation | Official docs | Multi-stage Dockerfile, Compose configuration |
| Claude (Anthropic) | Generative AI | Used to generate application code, Dockerfiles, deployment scripts, and this documentation. All output reviewed and tested manually. |