proxy through varnish

sets up varnish cache server with:
- custom cache per content type
- gzip
- configure app & hass backends
- varnish debug headers & X-Cache verbose cache header

also updates docker-compose, varnish/Dockerfile & drone.
This commit is contained in:
2025-08-21 23:41:26 +02:00
parent 8c79806318
commit 7d1f536af5
8 changed files with 294 additions and 9 deletions

View File

@@ -21,6 +21,8 @@ RUN git clone https://github.com/varnish/libvmod-digest.git /opt/libvmod-digest
./configure VARNISHSRC=/usr/include/varnish && \
make && make install
COPY . /etc/varnish/
EXPOSE 6081
CMD ["varnishd", "-F", "-f", "/etc/varnish/default.vcl", "-a", ":6081", "-s", "malloc,512m"]

146
varnish/default.vcl Normal file
View File

@@ -0,0 +1,146 @@
vcl 4.0;
import std;
import digest;
# include "handlers/ttl-override-handler.vcl";
include "includes/x-cache-header.vcl";
include "vcl_deliver.vcl";
# Define backend pointing to Home Assistant IP
backend hass_backend {
.host = "10.0.0.82";
.port = "8123";
}
backend app_frontend {
.host = "host.docker.internal";
.port = "5173";
}
sub vcl_recv {
set req.backend_hint = app_frontend;
# Handle CORS preflight
if (req.method == "OPTIONS") {
return (synth(204, "Preflight"));
}
# Rewrite image URL
if (req.url ~ "^/image-proxy/?") {
# Extract everything after /image-proxy/ and store it
set req.http.X-Image-URL = regsub(req.url, "^/image-proxy/(.*)", "\1");
# Rewrite req.url to match backend expectations
set req.url = regsub(req.http.X-Image-URL, "^http://[^/]+", "");
set req.backend_hint = hass_backend;
}
# Enable debug headers through query param
if (req.url ~ "(?i)debug=(true|yes|1)") {
set req.http.X-debug = true;
}
# Remove cookies so content is cacheable
unset req.http.Cookie;
}
sub vcl_synth {
if (resp.status == 204) {
set resp.http.Access-Control-Allow-Origin = "*";
set resp.http.Access-Control-Allow-Methods = "GET, OPTIONS";
set resp.http.Access-Control-Allow-Headers = "Content-Type, X-Cache-ID";
set resp.http.Content-Length = "0";
return (deliver);
}
if (resp.status == 304) {
set resp.http.ETag = req.http.If-None-Match;
set resp.http.Content-Length = "0";
return (deliver);
}
}
sub vcl_backend_response {
###################
# cache rules #
###################
# HTML pages → short cache or no cache
if (bereq.url ~ "\.html$") {
set beresp.ttl = 30s; # Cache briefly
set beresp.uncacheable = true; # Or disable cache entirely
}
# JavaScript & CSS → long cache
if (bereq.url ~ "\.(js|css)$") {
set beresp.ttl = 1d;
}
# Images under /image/ → long cache
if (bereq.url ~ "^/images/.*\.(svg|png|jpe?g)$") {
set beresp.ttl = 1y;
}
# Favicons → long cache
if (bereq.url ~ "^/favicons/") {
set beresp.ttl = 1y;
}
# Fallback: ensure some cache
if (beresp.ttl <= 0s) {
set beresp.ttl = 22s;
}
set beresp.http.X-TTL = beresp.ttl;
####################
# camera proxy #
####################
if (bereq.url ~ "^/api/camera_proxy/") {
set beresp.ttl = 0.3s;
set beresp.grace = 60s;
set beresp.keep = 60s;
# Ensure ETag is passed to client
if (beresp.http.ETag) {
set beresp.http.X-Cache-ETag = beresp.http.ETag;
} else {
# Optional: generate one if not provided
# set beresp.http.ETag = digest.hash_md5(beresp.body);
set beresp.http.ETag = beresp.http.Content-Length;
set beresp.http.X-Cache-ETag = beresp.http.ETag;
}
}
########################
# gzip compression #
########################
if (beresp.http.Content-Type ~ "^(text/|application/json|application/javascript|application/x-javascript|application/xml|application/xhtml+xml|image/svg\+xml)") {
set beresp.do_gzip = true;
}
return (deliver);
}
sub vcl_hit {
if (obj.ttl < 0s && std.healthy(req.backend_hint)) {
return (deliver);
}
}
sub vcl_deliver {
unset resp.http.X-Image-URL;
set resp.http.Access-Control-Allow-Origin = "*";
# Handle conditional request with ETag
if (
req.http.If-None-Match &&
req.http.If-None-Match == resp.http.ETag
) {
return (synth(304));
}
return (deliver);
}

View File

@@ -0,0 +1,43 @@
sub vcl_recv {
unset req.http.X-Cache;
}
sub vcl_hit {
set req.http.X-Cache = "HIT";
}
sub vcl_miss {
set req.http.X-Cache = "MISS";
}
sub vcl_pass {
set req.http.X-Cache = "PASS";
}
sub vcl_pipe {
set req.http.X-Cache = "PIPE uncacheable";
}
sub vcl_synth {
set resp.http.X-Cache = "SYNTH";
}
sub vcl_deliver {
if (obj.uncacheable) {
set req.http.X-Cache = req.http.X-Cache + " uncacheable" ;
} else {
set req.http.X-Cache = req.http.X-Cache + " cached" + " (real age: " + resp.http.Age + ", hits: " + obj.hits + ", ttl: " + resp.http.x-ttl + ")";
}
# if we are gracing, make sure the browser doesn't cache things, and set our maxage to 1
# also log grace delivery
if (req.http.graceineffect) {
set resp.http.Cache-Control = regsub(resp.http.Cache-Control, "max-age=[0-9]*", "max-age=1");
set resp.http.Cache-Control = regsub(resp.http.Cache-Control, "channel-maxage=[0-9]*", "channel-maxage=1");
set req.http.X-Cache = req.http.X-Cache + " [grace: " + req.http.graceineffect + " " + req.http.grace + ", remaining: " + req.http.graceduration + "]";
}
# uncomment the following line to show the information in the response
set resp.http.X-Cache = req.http.X-Cache;
}

40
varnish/vcl_deliver.vcl Normal file
View File

@@ -0,0 +1,40 @@
sub vcl_deliver {
# Happens when we have all the pieces we need, and are about to send the
# response to the client.
if (resp.status == 503) {
set resp.http.failing-backend = "true";
}
# Give some debug
if (req.http.X-debug && req.esi_level == 0) {
set resp.http.X-Backend = req.backend_hint;
set resp.http.X-Backend-Url = req.url;
} else {
# not debug, strip some headers
unset resp.http.X-Cache;
unset resp.http.X-Backend;
unset resp.http.x-upstream;
unset resp.http.x-request-uri;
unset resp.http.Via;
unset resp.http.xkey;
unset resp.http.x-goog-hash;
unset resp.http.x-goog-generation;
unset resp.http.X-GUploader-UploadID;
unset resp.http.x-goog-storage-class;
unset resp.http.x-goog-metageneration;
unset resp.http.x-goog-stored-content-length;
unset resp.http.x-goog-stored-content-encoding;
unset resp.http.x-goog-meta-goog-reserved-file-mtime;
unset resp.http.Server;
unset resp.http.X-Apache-Host;
unset resp.http.X-Varnish-Backend;
unset resp.http.X-Varnish-Host;
unset resp.http.X-Nginx-Host;
unset resp.http.X-Upstream-Age;
unset resp.http.X-Retries;
unset resp.http.X-TTL;
unset resp.http.X-Varnish;
}
}