Deploy on Azure
This guide walks you through deploying EnclaveStation on an Azure Virtual Machine. By the end, you'll have a running instance accessible over HTTPS.
Estimated time: 25–35 minutes
What you'll need
- An Azure account — create one here (includes $200 free credit for 30 days)
- A domain name (optional, but required for HTTPS)
- A local terminal with an SSH client
Architecture overview
EnclaveStation runs as three Docker containers orchestrated by Docker Compose:
Internet ──► Nginx (:80/:443)
├── / → React SPA (static files)
├── /api/* → C++ backend (:9001)
└── /ws → WebSocket to backend (:9001)
Backend (:9001) ──► PostgreSQL (:5432)Everything runs on a single Azure VM.
Step 1: Create a Virtual Machine
Using the Azure Portal
Open the Azure Portal
Click Create a resource → Virtual Machine → Create
Configure the Basics tab:
Setting Value Resource group Create new: enclave-station-rgVirtual machine name enclave-stationRegion Choose one close to your users Image Ubuntu Server 24.04 LTS - x64 Gen2 Size See table below Authentication type SSH public key Username azureuserVM size recommendations:
Size vCPUs RAM Best for Cost (approx.) Standard_B1s1 1 GB 1–10 users, light usage ~$8/mo Standard_B2s2 4 GB 10–50 users, recommended ~$31/mo Standard_B2ms2 8 GB 50+ users or heavy file uploads ~$61/mo Free tier
Azure's free tier includes 750 hours/month of
Standard_B1sfor the first 12 months.On the Disks tab, keep the default 30 GB Premium SSD
On the Networking tab:
- A new virtual network and public IP will be created automatically
- Under NIC network security group, select Advanced, then click Create new
- Add inbound rules for:
- HTTP: Port 80, Source: Any
- HTTPS: Port 443, Source: Any
- SSH (port 22) is added by default
Click Review + create → Create
When prompted, download the private key file (
.pem)
WARNING
Do not open port 5432 (PostgreSQL) or 9001 (backend). These should only be accessible internally between containers.
Using the Azure CLI
Alternatively, create everything with a single command:
az vm create \
--resource-group enclave-station-rg \
--name enclave-station \
--image Ubuntu2404 \
--size Standard_B2s \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-sku Standard \
--nsg-rule SSH
# Open HTTP and HTTPS ports
az vm open-port --resource-group enclave-station-rg --name enclave-station --port 80 --priority 1001
az vm open-port --resource-group enclave-station-rg --name enclave-station --port 443 --priority 1002Step 2: Get your public IP
Portal
Go to your VM's Overview page — the public IP is shown under Public IP address.
CLI
az vm show -d --resource-group enclave-station-rg --name enclave-station --query publicIps -o tsvNote this IP — you'll need it for DNS and SSH.
Static IP
By default, Azure assigns a static public IP when using Standard SKU. If you used Basic SKU, go to the Public IP address resource and set the Assignment to Static to prevent it from changing.
Step 3: Connect via SSH
chmod 400 ~/Downloads/enclave-station_key.pem
ssh -i ~/Downloads/enclave-station_key.pem azureuser@YOUR_PUBLIC_IPStep 4: Install Docker
# Update system packages
sudo apt update && sudo apt upgrade -y
# Install Docker using the official convenience script
curl -fsSL https://get.docker.com | sudo sh
# Add your user to the docker group
sudo usermod -aG docker $USER
# Apply group change
newgrp docker
# Verify
docker --version
docker compose versionStep 5: Clone and configure
# Clone the repository with submodules
git clone --recurse-submodules https://github.com/dariusjlukas/enclave-station.git
cd enclave-station
# Create your environment file from the example
cp .env.example .envEdit the .env file:
nano .env# IMPORTANT: Change this to a strong, unique password
POSTGRES_PASSWORD=your-secure-password-here
# Set this to your domain or public IP
PUBLIC_URL=https://chat.example.comGenerating a strong password
openssl rand -base64 24Step 6: Build and start
docker compose up -d --buildThe first build takes several minutes. Verify everything is running:
docker compose psYou should see three services all showing as running. Visit http://YOUR_PUBLIC_IP to confirm. The first user to register becomes the admin.
Step 7: Set up HTTPS with Let's Encrypt
Point your domain to the server
Create a DNS A record pointing to your Azure VM's public IP:
| Type | Name | Value |
|---|---|---|
| A | chat (or @ for root domain) | YOUR_PUBLIC_IP |
Verify propagation:
dig +short chat.example.comObtain a certificate
# Install certbot
sudo apt install -y certbot
# Stop containers so certbot can use port 80
docker compose down
# Obtain a certificate
sudo certbot certonly --standalone -d chat.example.comConfigure Nginx for SSL
cat > nginx/nginx-ssl.conf << 'NGINX'
server {
listen 80;
server_name _;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name _;
ssl_certificate /etc/letsencrypt/live/chat.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/chat.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
root /usr/share/nginx/html;
index index.html;
# API proxy
location /api/ {
proxy_pass http://backend:9001;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
client_max_body_size 0;
proxy_read_timeout 3600;
proxy_send_timeout 3600;
proxy_connect_timeout 60;
}
# WebSocket proxy
location /ws {
proxy_pass http://backend:9001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 86400;
}
# SPA fallback
location / {
try_files $uri $uri/ /index.html;
}
}
NGINXUpdate the frontend service in docker-compose.yml:
nano docker-compose.ymlReplace the frontend service with:
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
volumes:
- ./nginx/nginx-ssl.conf:/etc/nginx/conf.d/default.conf:z
- /etc/letsencrypt:/etc/letsencrypt:ro
ports:
- "80:80"
- "443:443"
depends_on:
- backendStart everything:
docker compose up -d --buildAutomatic certificate renewal
sudo crontab -eAdd:
0 3 * * * certbot renew --pre-hook "cd /home/azureuser/enclave-station && docker compose stop frontend" --post-hook "cd /home/azureuser/enclave-station && docker compose start frontend" >> /var/log/certbot-renew.log 2>&1Updating EnclaveStation
cd ~/enclave-station
git pull --recurse-submodules
docker compose up -d --buildBackups
Database
docker compose exec postgres pg_dump -U chatapp chatapp > backup-$(date +%Y%m%d).sqlUploaded files
docker cp $(docker compose ps -q backend):/data/uploads ./uploads-backupAutomating backups
mkdir -p ~/backups
sudo crontab -e0 2 * * * cd /home/azureuser/enclave-station && docker compose exec -T postgres pg_dump -U chatapp chatapp | gzip > /home/azureuser/backups/db-$(date +\%Y\%m\%d).sql.gz 2>&1Offsite backups with Azure Blob Storage
# Install the Azure CLI
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
# Upload backups
az storage blob upload-batch \
--destination your-container \
--account-name yourstorageaccount \
--source ~/backupsTroubleshooting
Can't connect from the browser
- Check the Network Security Group (NSG) rules allow inbound traffic on ports 80 and 443
- Verify containers are running:
docker compose ps - Test locally:
curl -I http://localhost
To check NSG rules via CLI:
az network nsg rule list --resource-group enclave-station-rg --nsg-name enclave-stationNSG -o tableVM runs out of memory
Check for OOM events:
dmesg | grep -i "oom\|killed"Resize the VM via the portal: VM → Size → select a larger size → Resize. This requires a restart.
Disk space issues
df -h
docker system df
docker system prune -aCosts
| Resource | Cost (approx.) |
|---|---|
Standard_B2s VM | ~$31/mo |
| 30 GB Premium SSD | ~$5/mo |
| Public IP (static) | ~$4/mo |
| Data transfer (first 100 GB/mo) | ~$8/mo |
| Let's Encrypt SSL | Free |
| Total | ~$48/mo |
Costs vary by region. Use the Azure Pricing Calculator for exact estimates. Consider Reserved Instances (1 or 3 year commitment) for significant savings.

