SIO Web Hosting Infrastructure
The website you're reading right now is served by our custom-built hosting infrastructure—a system we designed from the ground up to be more scalable, more secure, more cost-efficient, and significantly easier to manage than our previous setup. This infrastructure powers all SIOCODE websites, including this company site, our product sites, and the growing collection of applications at sio.sh.
In this post, we'll take you through the architecture, design decisions, and benefits of our SIO web hosting infrastructure.
The Challenge: Managing Multiple Static Sites
We maintain a growing portfolio of static websites and web applications:
- Company websites (siocode.hu, siocode.com)
- Product websites (projor.io, themazegame.net)
- Tool applications (qr.sio.sh, barcode.sio.sh, notes.sio.sh, and many more)
Each site needs:
- Fast, reliable hosting with minimal latency
- HTTPS with valid SSL certificates
- Easy deployment and updates
- The ability to preview changes before going live
- Cost-effective infrastructure that scales with our needs
Our previous infrastructure made adding new sites and updating configurations tedious and error-prone. We needed something better.
Architecture Overview
Our new infrastructure is built on three key components:
- Google Cloud Artifact Registry - Centralized package storage
- Compute Engine VMs - Ingress servers running nginx
- Cloud Build + Pub/Sub - Automated deployment orchestration
The entire system follows GitOps principles: infrastructure is defined in code, stored in GitHub, and deployed automatically.
The SIO Web Hosting Repository
Everything lives in a single Git repository hosted on GitHub. This repository contains:
/ingress- Scripts and configuration for ingress servers/operator- Node.js application managing deployments- Terraform configs - Infrastructure as code for staging and production
- Cloud Build triggers - Automated deployment pipelines
Let's dive into each component.
The Ingress Servers
Our ingress servers are Google Compute Engine VMs running Linux. The startup script of each VM fetches a zip package from Google Cloud Artifact Registry containing all the necessary scripts and configurations, downloads it, and extracts it to the correct location.
Startup Process
When a VM starts:
- The startup script fetches the latest ingress package from Artifact Registry
- Downloads and extracts it to the correct location
- Runs the setup script to install dependencies
The setup script installs:
- nginx - High-performance web server
- curl - For HTTP requests
- acme.sh - Certificate provisioning
- Node Version Manager (nvm) - Node.js version management
- Node.js - Runtime for the operator application
- All other necessary dependencies
This approach gives us complete control over the server environment and makes updates trivial—we just upload a new package version and restart the VM.
The Operator: Deployment Orchestration
The operator is a Node.js application responsible for:
- Deploying new sites
- Updating existing site content
- Managing SSL certificates
- Handling domain configuration
How It Works: Pub/Sub Messages
The operator listens to a Google Cloud Pub/Sub topic: site-deployment-request. Each deployment message contains:
{
"repository": "siocloud-generic",
"packageName": "siocode.hu",
"versionName": "v1.2.3",
"domains": [
"siocode.hu",
"www.siocode.hu",
"siocode.hu.production.serve.siocode.hu"
]
}
When a message arrives, the operator:
-
Checks if the site exists locally
- If new, creates site state tracking (version, certificate info)
- If existing, compares stored version with requested version
-
Performs DNS validation
- Resolves each domain to verify it points to our server
- Makes pure HTTP connections to test reachability
- Generates random challenge files to verify HTTP availability
- Ensures ACME challenges can be served
-
Provisions or updates SSL certificates
- Uses
acme.shwith Google's public ACME provider - Only provisions certificates for reachable domains
- Updates certificates if domain list changes
- Uses
-
Downloads and deploys site content
- Fetches the package from Artifact Registry using gcloud CLI
- Extracts to the appropriate nginx directory
- Generates nginx configuration
-
Reloads nginx
- Applies the new configuration
- Site goes live immediately
Intelligent Deployment Logic
The operator doesn't blindly redeploy everything. It only takes action when:
- The site is new
- The requested version differs from the deployed version
- The domain list has changed
This makes the system efficient—we can trigger "deploy all sites" without wasting resources on sites that haven't changed.
DNS Strategy: Staging and Production
We operate two environments:
- Staging -
*.staging.serve.siocode.hu - Production -
*.production.serve.siocode.hu
Each has its own Compute Engine VM and operator instance.
Wildcard Records for Easy Preview
We have wildcard A records configured:
*.staging.serve.siocode.hu → Staging server IP
*.production.serve.siocode.hu → Production server IP
This means any site deployed gets an immediate preview URL:
company-website.staging.serve.siocode.hucompany-website.production.serve.siocode.hu
No DNS configuration needed for preview URLs!
Production Domains: A and CNAME Records
For final production domains, we use:
- A records for apex/root domains (e.g.,
siocode.hu) - CNAME records for subdomains (e.g.,
www.siocode.hu,api.siocode.hu)
CNAME records point to the production serve URL:
www.siocode.hu CNAME → company-website.production.serve.siocode.hu
This gives us flexibility: we can deploy and test on the serve URL before updating the CNAME to make it live.
GitOps: Cloud Build Triggers
Our Terraform configuration automatically generates Cloud Build triggers for every site. We have two types:
Single Site Deployment Trigger
Each site gets its own trigger that:
- Queries Artifact Registry for the latest package version
- Sends a Pub/Sub message to
site-deployment-request - Waits for the operator to complete deployment
This allows us to deploy individual sites on-demand with a single click.
All Sites Deployment Trigger
One trigger to rule them all:
- Triggers every single-site deployment trigger
- Deploys all sites in parallel
- Only updates sites with version or domain changes
Perfect for infrastructure updates or mass rollouts.
Infrastructure Deployment Trigger
We also have triggers for deploying the infrastructure itself:
- Updates ingress server packages
- Modifies Pub/Sub topics and subscriptions
- Updates Cloud Build trigger definitions
- Applies Terraform changes
Everything is GitOps: commit to GitHub, trigger runs, infrastructure updates.
Unified Package Structure
Every static site becomes a versioned zip package in Artifact Registry. This gives us:
- Consistency - Every site follows the same package structure
- Versioning - Git tags become package versions
- Traceability - We always know what version is deployed where
- Rollback capability - Redeploy any previous version instantly
Since we use Cloud Build for all our builds, publishing these packages is trivial:
steps:
- name: 'node'
args: ['npm', 'run', 'build']
- name: 'gcr.io/cloud-builders/gcloud'
args:
- 'artifacts'
- 'generic'
- 'upload'
- '--source=company-website-next/siocode.hu.zip'
- '--package=siocode.hu'
- '--version=${TAG_NAME}'
- '--location=europe'
- '--repository=siocloud-generic'
Security Hardening
Our nginx configuration includes multiple security enhancements:
Method Restrictions
We only allow necessary HTTP methods for static sites:
limit_except GET HEAD OPTIONS {
deny all;
}
Static sites don't need POST, PUT, DELETE, etc. Blocking them reduces attack surface.
Minimal Client Body Size
We restrict request body sizes to prevent abuse:
client_max_body_size 1k;
This prevents certain attack vectors and reduces resource usage while still allowing minimal request headers.
Security Headers
Every response includes comprehensive security headers:
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header X-XSS-Protection "0" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
These headers protect against common web vulnerabilities:
- X-Content-Type-Options prevents MIME-sniffing attacks
- X-Frame-Options prevents clickjacking by disallowing iframe embedding
- X-XSS-Protection is set to "0" (disabled) as modern browsers have better built-in protection
- Referrer-Policy controls referrer information sent with requests
Directory Listing Disabled
We explicitly disable directory browsing:
autoindex off;
This prevents users from viewing directory contents.
Modern TLS Configuration
We enforce the latest TLS protocol:
ssl_protocols TLSv1.3;
ssl_prefer_server_ciphers off;
Using only TLSv1.3 ensures the strongest encryption standards.
SSL Stapling
We enable OCSP stapling for improved SSL performance and privacy:
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
HTTPS Enforcement
All sites redirect HTTP to HTTPS:
server {
listen 80;
server_name example.com;
location / {
limit_except GET HEAD OPTIONS {
deny all;
}
return 301 https://$host$request_uri;
}
}
Ensuring all traffic is encrypted.
Benefits of the New Infrastructure
Scalability
Adding a new site is incredibly easy:
- Create the site and configure CI/CD to upload it to Artifact Registry
- Add it to the staging list of sites in the Terraform configuration
- Run staging infrastructure deployment - This creates the deployment trigger for the site
- Run the staging site deployment trigger - Test that everything works on staging
- Add it to the production list of sites in the Terraform configuration
- Run production infrastructure deployment - This creates the production deployment trigger
- Run the production site deployment trigger - Deploy the site to its final location
Done. The operator handles everything else.
Cost Efficiency
We pay only for:
- Two small Compute Engine VMs (staging + production)
- Artifact Registry storage (negligible for static sites)
- Minimal egress (most sites have low traffic)
Compared to managed solutions like Firebase Hosting or Netlify at scale, we save significantly.
Easy Updates
To update a site:
- Commit changes to Git
- Tag with new version
- Cloud Build automatically uploads new package
- Click deployment trigger
The operator downloads the new version and deploys it. Total time: seconds.
Infrastructure as Code
Every piece of infrastructure is defined in Terraform:
- Compute Engine VMs
- Pub/Sub topics and subscriptions
- Cloud Build triggers
- IAM permissions
We can recreate the entire infrastructure from scratch with a single terraform apply.
Unified Management
One repository, one workflow, consistent patterns across all sites. No more scattered configuration files or manual nginx edits.
Conclusion
Building our own hosting infrastructure was a significant investment, but it's paid off immensely. We have a system that's:
- Scalable - Adding sites is trivial
- Secure - Security hardening built-in
- Cost-efficient - Fraction of the cost of managed solutions
- Easy to manage - GitOps workflow makes everything smooth
- Reliable - Proven infrastructure components (nginx, acme.sh, Google Cloud)
If you're managing multiple static sites and want complete control over your infrastructure, consider a similar approach. The flexibility and cost savings are substantial.
Interested in learning more about our infrastructure or need help building something similar? Contact us at info@siocode.hu.
