Heroku: Migrating Buildpacks

Nuh

NOTE: this post was updated in 2024 to keep its references accurate

How do you migrate from heroku-buildpack-static (recently deprecated) to nginx-buildpack?

I am writing this little note because the instructions provided by Heroku seemed a little too cryptic for a non-devops person.

Here’s how I resolved mine. My first attempt.

To make it short, the answer is really to copy Dan Andreasson ’s here and make the modification he made in the next comment regarding the order of the buildpacks on heroku.

For the long story, we explain what was cryptic about the heroku instructions.

mruby

When Heroku said replace mruby with static path logic, what it meant was to remove all lines to do with mruby and delete mruby’s responsibility to run a script that captured all paths and formatted them. That means, you now need a new way to generate those same paths that came from your static.json.

Dan then showed us how by first duplicating the location config and adding RegEx for path route/path filtering.

Last but not least, all we need now is to remove our nodejs buildpack and read it so that Heroku runs correctly. I am not sure which of node and nginx-static Heroku handles first during deployment.

That’s all.


While writing the above, I thought I understood it all under control but then I ended up spending the rest of the day puzzled and only resolved it in the evening after 10-15 redeploys.

It was a round trip of messing with dynamic ports, crashing the app, finally then fixing the nginx port issues only to face head-on a simple and mild error about $path.

location ~ ^/.*$ {
    set $route /.*;

    try_files $uri $path/;
}

This unknown path variable was copied over from Dan’s thinking it was automatically injected, it was at this time I realised why most examples were hardcoding paths into their configs. So I removed it and was about to leave it to just / but then it looked the same as the location filter above it that I had and removed the entire block.

Just so I can have everything dynamic, I copied over Daniel’s config and modified it to point at the right stuff for my deployment. You can find my complete nginx.conf.erb below.

daemon off;
# Heroku dynos have at least 4 cores.
worker_processes <%= ENV['NGINX_WORKERS'] || 4 %>;

events {
	use epoll;
	accept_mutex on;
	worker_connections <%= ENV['NGINX_WORKER_CONNECTIONS'] || 1024 %>;
}

http {
	gzip on;
	gzip_comp_level 2;
	gzip_min_length 512;
	gzip_proxied any; # Heroku router sends Via header

	server_tokens off;

	log_format l2met 'measure#nginx.service=$request_time request_id=$http_x_request_id';
	access_log <%= ENV['NGINX_ACCESS_LOG_PATH'] || 'logs/nginx/access.log' %> l2met;
	error_log <%= ENV['NGINX_ERROR_LOG_PATH'] || 'logs/nginx/error.log' %>;


	include mime.types;
	default_type application/octet-stream;
	sendfile on;

	# Must read the body in 5 seconds.
	client_body_timeout <%= ENV['NGINX_CLIENT_BODY_TIMEOUT'] || 5 %>;

	server {
		listen <%= ENV["PORT"] %>;
		server_name _;
		keepalive_timeout 5;
		client_max_body_size <%= ENV['NGINX_CLIENT_MAX_BODY_SIZE'] || 1 %>M;

		root public/;

        error_page 404 500 /404.html;

        location = / {

            try_files $uri $uri/;

        }

        if ($http_x_forwarded_proto != "https") {
            return 301 https://$host$request_uri;
        }

        # need this b/c setting $fallback to =404 will try #{root}=404 instead of returning a 404
        location @404 {
            return 404;
        }
	}
}

P.S. This post was first written so I can have something to commit and deploy after changing the buildpack order in Heroku

Nuh © 2024