├── root └── etc │ ├── confd │ ├── conf.d │ │ └── default.vcl.toml │ └── templates │ │ └── default.vcl.tmpl │ └── services.d │ └── varnish │ └── run ├── Dockerfile └── README.md /root/etc/confd/conf.d/default.vcl.toml: -------------------------------------------------------------------------------- 1 | [template] 2 | src = "default.vcl.tmpl" 3 | dest = "/etc/varnish/default.vcl" 4 | uid = 0 5 | gid = 0 6 | mode = "0644" 7 | keys = [] 8 | -------------------------------------------------------------------------------- /root/etc/services.d/varnish/run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/with-contenv sh 2 | 3 | exec /usr/sbin/varnishd \ 4 | -F \ 5 | -f /etc/varnish/default.vcl \ 6 | -a ${VARNISH_LISTEN_ADDRESS}:${VARNISH_LISTEN_PORT} \ 7 | -T ${VARNISH_ADMIN_LISTEN_ADDRESS}:${VARNISH_ADMIN_LISTEN_PORT} \ 8 | -s $VARNISH_STORAGE \ 9 | $VARNISH_EXTRA_OPTS 10 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM outrigger/servicebase 2 | 3 | RUN yum -y update && \ 4 | yum -y install varnish && \ 5 | yum clean all 6 | 7 | EXPOSE 80 8 | 9 | ENV VARNISH_LISTEN_ADDRESS 0.0.0.0 10 | ENV VARNISH_LISTEN_PORT 80 11 | 12 | # Set the control terminal to be wide open by default, with no secret file. 13 | ENV VARNISH_ADMIN_LISTEN_ADDRESS 0.0.0.0 14 | ENV VARNISH_ADMIN_LISTEN_PORT 6082 15 | 16 | ENV VARNISH_STORAGE "malloc,64M" 17 | 18 | # A catch-all for any other options. 19 | ENV VARNISH_EXTRA_OPTS "" 20 | 21 | ENV VARNISH_BACKEND_HOST www 22 | ENV VARNISH_BACKEND_PORT 80 23 | 24 | ADD root / 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Outrigger Varnish 3 | 4 | > Varnish 4 container with a confd template that outputs a nice Drupal-compatible VCL 5 | 6 | [![](https://images.microbadger.com/badges/version/outrigger/varnish.svg)](https://microbadger.com/images/outrigger/varnish "Get your own version badge on microbadger.com") [![](https://images.microbadger.com/badges/image/outrigger/varnish.svg)](https://microbadger.com/images/outrigger/varnish "Get your own image badge on microbadger.com") 7 | 8 | This CentOS-based Varnish image has deep Drupal support and a number of 9 | configurable options. 10 | 11 | For more documentation on how Outrigger images are constructed and how to work 12 | with them, please [see the documentation](http://docs.outrigger.sh/en/latest/). 13 | 14 | ## Features 15 | 16 | ### PURGE & PURGEALL 17 | 18 | Support for HTTP request-based PURGING of cached data. 19 | 20 | * PURGE is used to clear the specific URL. 21 | * PURGEALL will wipe all content for the requested host. 22 | 23 | PURGE requests may come from the Varnish local host, or the docker0 bridge network. 24 | 25 | ### Cache Tag Invalidation 26 | 27 | Uses Varnish bans to clear cache based on Drupal 8 Cache tags via 28 | [Purge](https://www.drupal.org/project/purge) module. 29 | 30 | Applies the same access control as Purge uses. 31 | 32 | ## Environment Variables 33 | 34 | Outrigger images use Environment Variables and [confd](https://github.com/kelseyhightower/confd) 35 | to "templatize" a number of Docker environment configurations. These templates are 36 | processed on startup with environment variables passed in via the docker run 37 | command-line or via your docker-compose manifest file. Here are the "tunable" 38 | configurations offered by this image. 39 | 40 | * `VARNISH_BACKEND_HOST`: [`www`] Hostname Varnish uses for its backend. 41 | * `VARNISH_BACKEND_PORT`: [`80`] Port number for the Varnish backend. 42 | * `VARNISH_LISTEN_ADDRESS`: [`0.0.0.0`] IP address on which Varnish listens for 43 | requests to proxy. 44 | * `VARNISH_LISTEN_PORT`: [`80`]: Port on which Varnish listens for requests to 45 | proxy. 46 | * `VARNISH_ADMIN_LISTEN_ADDRESS`: [`0.0.0.0`] Wide open access to Varnish control. 47 | * `VARNISH_ADMIN_LISTEN_PORT`: [`6082`] Port at which to access Varnish admin. 48 | * `VARNISH_STORAGE`: [`"malloc,64M"`] Further storage configuration. 49 | * `VARNISH_EXTRA_OPTS`: [`""`] Miscellaneous catch-all options passed to Varnish 50 | at container start. Empty by default. 51 | 52 | ## Security Reports 53 | 54 | Please email outrigger@phase2technology with security concerns. 55 | 56 | ## Maintainers 57 | 58 | [![Phase2 Logo](https://www.phase2technology.com/wp-content/uploads/2015/06/logo-retina.png)](https://www.phase2technology.com) 59 | -------------------------------------------------------------------------------- /root/etc/confd/templates/default.vcl.tmpl: -------------------------------------------------------------------------------- 1 | # This is is required for all Varnish 4 VCL configurations. 2 | vcl 4.0; 3 | 4 | # The access control list from which PURGE requests can come. 5 | acl purge { 6 | "localhost"; 7 | "127.0.0.1"; 8 | "::1"; 9 | # The full range of possible IP's with the docker0 bridge interface. 10 | "172.17.0.0/16"; 11 | } 12 | 13 | # Set up a configuration to connect to "www" on port 80. 14 | backend default { 15 | .host = "{{ getenv "VARNISH_BACKEND_HOST" }}"; 16 | .port = "{{ getenv "VARNISH_BACKEND_PORT" }}"; 17 | .connect_timeout = 120s; 18 | .first_byte_timeout = 120s; 19 | .between_bytes_timeout = 120s; 20 | } 21 | 22 | sub vcl_recv { 23 | unset req.http.proxy; 24 | 25 | # Create or add to an X-Forwarded-For header. 26 | if (req.restarts == 0) { 27 | if (req.http.X-Forwarded-For) { # set or append the client.ip to X-Forwarded-For header 28 | set req.http.X-Forwarded-For = req.http.X-Forwarded-For + ", " + client.ip; 29 | } else { 30 | set req.http.X-Forwarded-For = client.ip; 31 | } 32 | } 33 | 34 | 35 | # Allow purging with the PURGE HTTP verb. 36 | if (req.method == "PURGE" || req.method == "PURGEALL") { 37 | if (!client.ip ~ purge) { 38 | return (synth(403, "Your host is not allowed to PURGE content.")); 39 | } 40 | 41 | if (req.method == "PURGEALL") { 42 | // Purge all objects from cache that match the incoming host 43 | ban ("req.url ~ ^/ && req.http.host == " + req.http.host); 44 | return (synth(200, "Purged")); 45 | } 46 | 47 | return (purge); 48 | } 49 | 50 | # Allow bans for the same people purges are allowed for 51 | if (req.method == "BAN") { 52 | if (!client.ip ~ purge) { 53 | return (synth(403, "Your host is not allowed to BAN content.")); 54 | } 55 | 56 | // Purge-Cache-Tags is the header set by the purge module 57 | if (req.http.Purge-Cache-Tags) { 58 | // note that a cache tag which is a substring of another cache 59 | // tag will result in a more broad than intended ban but does 60 | // allow for convenient all objects of type banning possibilities. 61 | // Would need to add word boundary constraints on match to prevent 62 | // that if desired 63 | ban ("obj.http.Purge-Cache-Tags ~ " + req.http.Purge-Cache-Tags); 64 | } 65 | else { 66 | return (synth(403, "Purge-Cache-Tags header missing.")); 67 | } 68 | 69 | # Return a synthetic page so the request won't go to the backend. 70 | return (synth(200, "Ban Added")); 71 | } 72 | 73 | if (req.method != "GET" && 74 | req.method != "HEAD" && 75 | req.method != "PUT" && 76 | req.method != "POST" && 77 | req.method != "TRACE" && 78 | req.method != "OPTIONS" && 79 | req.method != "DELETE") { 80 | # Non-RFC2616 or CONNECT which is weird. 81 | return (pipe); 82 | } 83 | 84 | if (req.method != "GET" && req.method != "HEAD") { 85 | # We only deal with GET and HEAD by default. 86 | return (pass); 87 | } 88 | 89 | # Whitelist only the Drupal session cookie (secure or otherwise). 90 | if (req.http.cookie) { 91 | set req.http.cookie = ";" + req.http.cookie; 92 | set req.http.cookie = regsuball(req.http.cookie, "; +", ";"); 93 | set req.http.cookie = regsuball(req.http.cookie, ";(S?SESS[a-z0-9]+)=", "; \1="); 94 | set req.http.cookie = regsuball(req.http.cookie, ";[^ ][^;]*", ""); 95 | set req.http.cookie = regsuball(req.http.cookie, "^[; ]+|[; ]+$", ""); 96 | } 97 | 98 | # Remove a ";" prefix, if present. 99 | set req.http.cookie = regsub(req.http.cookie, "^;\s*", ""); 100 | 101 | # Remove empty cookies. 102 | if (req.http.cookie ~ "^\s*$") { 103 | unset req.http.cookie; 104 | } 105 | 106 | # Skip the Varnish cache for install, update, and cron 107 | if (req.url ~ "install\.php|update\.php|cron\.php") { 108 | return (pass); 109 | } 110 | 111 | if (req.url ~ "\.(aif|aiff|au|avi|bin|bmp|cab|carb|cct|cdf|class|css|dcr|doc|dtd|eot|exe|flv|gcf|gff|gif|grv|hdml|hqx|html|ico|ini|jpeg|jpg|js|mov|mp3|nc|pct|pdf|pdf|png|ppc|pws|otf|svg|swa|swf|swf|ttf|txt|vbs|w32|wav|wbmp|wml|wmlc|wmls|wmlsc|woff|xml|xsd|xsl|zip)") { 112 | # Don't use Cookie variance for static files. This will let authenticated users 113 | # get cache hits. 114 | unset req.http.cookie; 115 | } 116 | 117 | return (hash); 118 | } 119 | 120 | sub vcl_hash { 121 | # This allows Varnish to work properly behind an SSL-terminating proxy. 122 | if (req.http.x-forwarded-proto) { 123 | hash_data(req.http.x-forwarded-proto); 124 | } 125 | 126 | # This will allow Varnish to cache a site with basic auth. 127 | if (req.http.authorization) { 128 | hash_data(req.http.authorization); 129 | } 130 | 131 | # This ensures that Varnish does not have a problem distinguishing anonymous and authenticated. 132 | if (req.http.cookie) { 133 | hash_data(req.http.cookie); 134 | } 135 | } 136 | 137 | sub vcl_backend_response { 138 | # Add an X-Host and X-URL header for bans. 139 | set beresp.http.x-host = bereq.http.host; 140 | set beresp.http.x-url = bereq.url; 141 | # Strip any cookies before an image/js/css is inserted into cache. 142 | if (bereq.url ~ "\.(aif|aiff|au|avi|bin|bmp|cab|carb|cct|cdf|class|css|dcr|doc|dtd|eot|exe|flv|gcf|gff|gif|grv|hdml|hqx|html|ico|ini|jpeg|jpg|js|mov|mp3|nc|pct|pdf|png|ppc|pws|otf|svg|swa|swf|swf|ttf|txt|vbs|w32|wav|wbmp|wml|wmlc|wmls|wmlsc|woff|xml|xsd|xsl|zip)"){ 143 | unset beresp.http.set-cookie; 144 | } 145 | 146 | # Make sure a browser would never cache an uncacheable (e.g. 50x) response. 147 | if (beresp.ttl <= 0s) { 148 | set beresp.http.Cache-Control = "no-cache, no-store, must-revalidate"; 149 | } else if (beresp.http.Content-Type ~ "text/html" && beresp.ttl > 0s) { 150 | # Set a short TTL for HTML to force re-updating against the Drupal page cache. 151 | set beresp.ttl = 1m; 152 | } 153 | 154 | # Keep this around for 24 hours past its TTL. This will allow Varnish 4 to 155 | # re-update it in the background. 156 | set beresp.grace = 24h; 157 | set beresp.keep = 24h; 158 | 159 | # You _may_ want to return a hit-for-pass here, or disable it, depending on 160 | # if you expect to have a lot of non-cacheable responses. 161 | # 162 | # The default here will fall through to Varnish's default processing logic 163 | # which will sent a 2 minute hit-for-pass if this URL returns an uncacheable 164 | # response. 165 | # 166 | # Comment out the following line to always hit-for-pass. 167 | return (deliver); 168 | } 169 | 170 | sub vcl_deliver { 171 | # Don't output the headers that are used for ban purposes. 172 | unset resp.http.x-host; 173 | unset resp.http.x-url; 174 | unset resp.http.Purge-Cache-Tags; 175 | } 176 | --------------------------------------------------------------------------------