{"componentChunkName":"component---src-templates-blog-post-js","path":"/blog/publishing-screenshots-screencasts-and-other-files-on-digitalocean-spaces","result":{"data":{"allGhostPost":{"edges":[{"node":{"title":"Publishing Screenshots, Markdown Documents, and Other Files on DigitalOcean Spaces","html":"
\nA couple of weeks ago at DigitalOcean, we introduced Spaces—beautifully simple and reliable object storage. With Spaces, you can host and serve static files without having to worry about security, scaling, disk space, and all the fun things that come with maintaining a server.
\nSpaces is compatible with the S3 API. This is important because it makes Spaces fully available to use with a huge number of applications and tools right away. In this post, I'll talk about how I'm using Dropshare to share screenshots, markdown files (rendered as HTML!), and all sorts of files on a custom domain name backed by Spaces.
\nThe most important part about this is that you are able to use your own domain name instead of bucket.region.digitaloceanspaces.com
. But that's not all. In addition to simply serving static files, kmlnsr.me
also renders markdown files as HTML, with the ability to view the raw Markdown source code. Take a look at this file for example. It's a Markdown file that is rendered as HTML with styling, on the fly. You can view the original Markdown code by appending /raw to the URL. Spaces does not support that natively, though. More on how that works in a bit.
Dropshare uses the S3 API to upload files to Spaces. It supports custom S3 endpoints natively, so it's very easy to configure. Simply create a new connection with the following configuration:
\n\nYou might also want to replace the default screenshot hotkey to use Dropshare. First, disable the default hotkey in System Preferences→Keyboard→Shortcuts→Screen Shots. Then, configure Dropshare to use that shortcut in the Screenshots tab.
\nFor reference, here's Dropshare documentation on setting up DigitalOcean Spaces connections:
\n\nNow this diagram is a bit more complex than the previous one, so let's break it down piece by piece.
\nA browser makes a request to a file on kmlnsr.me
(like the screenshot above!). The request goes to a Droplet with nginx running on it.
Nginx looks at the URL and checks if it is a Markdown file.
\na. If it is not a Markdown file, it proxies the request to Spaces, fetching the file and serving it as is to the browser.
\nb. If it is a Markdown file, it fetches the file from Spaces, passes it to a markdown renderer, and then serves the HTML version to the browser.
\nLet's build the configuration bit by bit, starting with a basic one:
\nserver {\n listen 80;\n\n server_name kmlnsr.me;\n root /dev/null;\n \n location = / {\n return 302 https://kamal.io;\n }\n}\n
\nRight now, it doesn't do much. http://kmlnsr.me/
redirects to https://kamal.io
, and everything else returns a 404 Not Found
error.
The idea behind serving static files is fairly simple. Proxy requests to https://kmlnsr.nyc3.digitaloceanspaces.com
without any post-processing.
Because we will set up a couple of location
blocks, let's extract the common directives into their own file that we can simply include wherever we need it. I saved it in /etc/nginx/snippets/spaces_proxy.conf
.
proxy_http_version 1.1;\nproxy_set_header Authorization '';\nproxy_hide_header x-amz-id-2;\nproxy_hide_header x-amz-request-id;\nproxy_hide_header Set-Cookie;\nproxy_ignore_headers "Set-Cookie";\nproxy_intercept_errors on;\n
\nNow, let's serve some static files! location /
matches all requests—except /
itself as it is matched by a previous location
block.
location / {\n include snippets/spaces_proxy.conf;\n \n proxy_set_header Host kmlnsr.nyc3.digitaloceanspaces.com;\n proxy_pass https://kmlnsr.nyc3.digitaloceanspaces.com;\n}\n
\nThe proxy_set_header
directive is important. Without it, Spaces would receive kmlnsr.me
as the Host
and wouldn't know what bucket it is, as it doesn't understand custom domain names.
With this config, everything should work as if you are accessing https://YOUR_SPACE.nyc3.digitaloceanspaces.com
directly.
There are two parts to this feature:
\nRight now, if you browse to /markdown_file.md/raw
, you would get a 404 Not Found
error. This is expected, because you are trying to access a file named raw
in the directory /markdown_file.md
.
In order to fix that, we need to modify the URL that is passed to Spaces, removing the /raw
bit. This can easily be done with a bit of RegEx magic.
location ~ ^/(.*)\\.md/raw$ {\n include snippets/spaces_proxy.conf;\n \n add_header Content-Type text/plain;\n proxy_set_header Host kmlnsr.nyc3.digitaloceanspaces.com;\n proxy_pass https://kmlnsr.nyc3.digitaloceanspaces.com/$1.md;\n}\n
\nThe URI pattern ^/(.*)\\.md/raw$
matches requests in the format of */markdown_file.md/raw
, capturing the path to the Markdown file in the process. Once we have that, we simply set the URL that is sent to Spaces to */markdown_file.md
.
This way, we get the file's content, and serve it back to the browser. Setting the Content-Type
header to text/plain
asks browsers to display the file as plain text instead of downloading it.
I've had this set up for years, so it uses a simple PHP script to do the hard work. You can download the source code here, hosted on Spaces of course :)
\nIn that archive, you will find two files:
\nmarkdown.php
this is the Markdown library that I'm using. It's a very old version, but it has been working well so why bother replacing it ¯\\_(ツ)_/¯md.php
this is the file that Nginx runs. It's not super complex, but it basically requests the source (using the /raw
URL), parses it using the Markdown library, and serves it along with a basic CSS stylesheet. Unfortunately, I don't remember where I got the stylesheet from.You will need to adjust a couple of things in md.php
. First, set the $url
variable (line 5) to your own hostname. Then, copy the stylesheet to your Space and update the URL on line 21.
location ~ \\.md$ {\n fastcgi_pass unix:/var/run/php5-fpm.sock;\n fastcgi_param DOCUMENT_ROOT $document_root;\n fastcgi_param SCRIPT_NAME $uri;\n fastcgi_param SCRIPT_FILENAME /srv/markdown/md.php;\n include fastcgi_params;\n}\n
\nThis is a basic php-fpm configuration. The most important part is that SCRIPT_FILENAME
is fixed and always set to the md.php
handler.
Once you're all done, the config file will look like this:
\nserver {\n listen 80;\n\n server_name kmlnsr.me;\n root /dev/null;\n \n location = / {\n return 302 https://kamal.io;\n }\n \n location / {\n include snippets/spaces_proxy.conf;\n \n proxy_set_header Host kmlnsr.nyc3.digitaloceanspaces.com;\n proxy_pass https://kmlnsr.nyc3.digitaloceanspaces.com;\n }\n\n location ~ ^/(.*)\\.md/raw$ {\n include snippets/spaces_proxy.conf;\n \n add_header Content-Type text/plain;\n proxy_set_header Host kmlnsr.nyc3.digitaloceanspaces.com;\n proxy_pass https://kmlnsr.nyc3.digitaloceanspaces.com/$1.md;\n }\n\n location ~ \\.md$ {\n fastcgi_pass unix:/var/run/php5-fpm.sock;\n fastcgi_param DOCUMENT_ROOT $document_root;\n fastcgi_param SCRIPT_NAME $uri;\n fastcgi_param SCRIPT_FILENAME /srv/markdown/md.php;\n include fastcgi_params;\n }\n}\n
\nThat's all there is to it! Reload Nginx and you should be all set.
\nCheck this out. Who wouldn't want that over this?! Luckily, this is super easy to set up. All you need to do is add the following to your server block.
\nproxy_intercept_errors on;\nerror_page 403 /404.html;\nerror_page 404 /404.html;\nlocation = /404.html {\n root /srv/www;\n}\n
\nFinally, put whatever 404 page you want in /srv/www/404.html
and Nginx will serve it whenever a 404 or 403 error occurrs, which S3-compatible endpoints tend to send when files are not found.
You will probably want to set up some kind of caching in order to reduce latency. Add the following to /etc/nginx/nginx.conf
:
proxy_cache_path /srv/www/spaces/cache levels=1:2 keys_zone=SPACES:50m inactive=168h max_size=10g;\n
\nYou can set the path to whatever you want, of course. Just make sure that www-data
has write access to it.
Then, like we did with the common S3 directives, save the following in /etc/nginx/snippets/spaces_cache.conf
.
add_header X-Cache-Status $upstream_cache_status;\nproxy_cache SPACES;\nproxy_cache_valid 1d;\nproxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;\n
\nThis will cache files for one day. Because every file has a random string appended to it, I don't really worry about the cache period being that long, but you might want to lower it.
\nNow, go back to your server block, and add the following to every block that has a proxy_pass
directive to Spaces.
include snippets/spaces_cache.conf;\n
\nOk, this is not so optional really. Please set up SSL. With Let's Encrypt, you really have no excuse not to do so.
\n\nI use my own short URL for files, kmln.sr
. This lets me have neat links like https://kmln.sr/qa6. klein is a super easy-to-set-up URL shortener that I wrote and use. The README should provide you with all the necessary documentation. Once that's set up, you can configure Dropshare to use it under the Uploads tab like so: