ZKT26/SK1
2026-05-13 09:22:18 +02:00
..
backend SK1 2026-05-12 15:19:22 +02:00
frontend SK1 2026-05-12 15:19:22 +02:00
scripts jewn 2026-05-13 09:22:18 +02:00
docker-compose.yml SK1 2026-05-12 15:19:22 +02:00
README.md SK1 2026-05-12 15:19:22 +02:00

🔐 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
  • .env file filled in

Deploy

cd sk1/
bash scripts/prepare-app.sh

This script automatically:

  1. Creates the Azure resource group
  2. Provisions the VM (Standard_D2s_v3, West Europe)
  3. Creates PostgreSQL Flexible Server (B1MS, North Europe)
  4. Creates Azure Blob Storage for backups
  5. Installs Docker on the VM
  6. Copies app files to the VM
  7. 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, .env present, SSH key at ~/.ssh/id_rsa, no existing pastevault-rg resource group
  • remove-app.sh: Azure CLI logged in, SSH access to VM still working

Using the Application

  1. Open the app URL in a browser (https://yourdomain.com)
  2. Enter a title, paste your code or text into the editor
  3. Select the language for syntax highlighting
  4. Choose an expiry time (or never)
  5. Optionally set a password
  6. Click "🔒 Create Paste"
  7. You are redirected to the paste URL — copy it with the "🔗 Share" button
  8. 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, and Standard_B2ats_v2 had capacity restrictions across multiple European regions. Resolved by using Standard_D2s_v3 in West Europe.
  • PostgreSQL SSL configuration conflictasyncpg does not accept ?ssl=require in the connection URL when also passed as a keyword argument. Resolved by keeping ssl=True in code only and removing it from the DATABASE_URL.
  • Docker Compose version field warning — Docker Compose v2 treats the version key 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.