mirror of
https://github.com/LCTT/TranslateProject.git
synced 2024-12-26 21:30:55 +08:00
237 lines
11 KiB
Markdown
237 lines
11 KiB
Markdown
|
[#]: subject: "Hosting my static sites with nginx"
|
|||
|
[#]: via: "https://jvns.ca/blog/2022/01/24/hosting-my-static-sites-with-nginx/"
|
|||
|
[#]: author: "Julia Evans https://jvns.ca/"
|
|||
|
[#]: collector: "lujun9972"
|
|||
|
[#]: translator: " "
|
|||
|
[#]: reviewer: " "
|
|||
|
[#]: publisher: " "
|
|||
|
[#]: url: " "
|
|||
|
|
|||
|
Hosting my static sites with nginx
|
|||
|
======
|
|||
|
|
|||
|
Hello! Recently I’ve been thinking about putting my static sites on servers that I run myself instead of using managed services like Netlify or GitHub Pages.
|
|||
|
|
|||
|
Originally I thought that running my own servers would require a lot of maintenance and be a huge pain, but I was chatting with Wesley about what kind of maintainance [their servers][1] require, and they convinced me that it might not be that bad.
|
|||
|
|
|||
|
So I decided to try out moving all my static sites to a $5/month server to see what it was like.
|
|||
|
|
|||
|
Everything in here is pretty standard but I wanted to write down what I did anyway because there are a surprising number of decisions and I like to see what choices other people make.
|
|||
|
|
|||
|
### the constraint: only static sites
|
|||
|
|
|||
|
To keep things simple, I decided that this server would only run `nginx` and only serve static sites. I have about 10 static sites right now, mostly projects for [wizard zines][2].
|
|||
|
|
|||
|
I decided to use a $5/month DigitalOcean droplet, which should very easily be able to handle my existing traffic (about 3 requests per second and 100GB of bandwidth per month). Right now it’s using about 1% of its CPU. I picked DigitalOcean because it was what I’ve used before.
|
|||
|
|
|||
|
Also all the sites were already behind a CDN so they’re still behind the same CDN.
|
|||
|
|
|||
|
### problem 1: getting a clean Git repo for each build
|
|||
|
|
|||
|
This was the most interesting problem so let’s talk about it first!
|
|||
|
|
|||
|
Building the static sites might seem pretty easy – each one of them already has a working build script.
|
|||
|
|
|||
|
But I have pretty bad hygiene around files on my laptop – often I have a bunch of uncommitted files that I don’t want to go onto the live site. So I wanted to start every build with a clean Git repo. I also wanted this to be _fast_ – I’m impatient so I wanted to be able to build and deploy most of my sites in less than 10 seconds.
|
|||
|
|
|||
|
I handled this by hacking together a tiny build system called [tinybuild][3]. It’s basically a 4-line bash script, but with extra some command line arguments and error checking. Here are the 4 lines of bash:
|
|||
|
|
|||
|
```
|
|||
|
|
|||
|
docker build - -t tinybuild < Dockerfile
|
|||
|
CONTAINER_ID=$(docker run -v "$PWD":/src -v "./deploy:/artifact" -d -t tinybuild /bin/bash)
|
|||
|
docker exec $CONTAINER_ID bash -c "git clone /src /build && cd /build && bash /src/scripts/build.sh"
|
|||
|
docker exec $CONTAINER_ID bash -c "mv /build/public/* /artifact"
|
|||
|
|
|||
|
```
|
|||
|
|
|||
|
These 4 lines:
|
|||
|
|
|||
|
1. Build a Dockerfile with all the dependencies for that build
|
|||
|
2. Clone my repo into `/build` in the container, so that I always start with a clean Git repo
|
|||
|
3. Run the build script (`/src/scripts/build.sh`)
|
|||
|
4. Copy the build artifacts into `./deploy` in the local directory
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Then once I have `./deploy`, I can rsync the result onto the server
|
|||
|
|
|||
|
It’s fast because:
|
|||
|
|
|||
|
* the `docker build -` means I don’t send any state from the repository to the Docker daemon. This matters because one of my repos is 1GB (it has a lot of PDFs in it) and sending all that to the Docker daemon takes forever
|
|||
|
* the `git clone` is from the local filesystem and I have a SSD so it’s fast even for a 1GB repo
|
|||
|
* most of the build scripts just run `hugo` or `cat` so they’re fast. The `npm` build scripts take maybe 30 seconds.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
### apparently local git clones make hard links
|
|||
|
|
|||
|
A tiny interesting fact: I tried to do `git clone --depth 1` to speed up my git clone, but git gave me this warning:
|
|||
|
|
|||
|
```
|
|||
|
|
|||
|
warning: --depth is ignored in local clones; use file:// instead.
|
|||
|
|
|||
|
```
|
|||
|
|
|||
|
I think what’s going on here is that git makes hard links of all the objects to make a local clone (which is a lot faster than copying). So I guess with the hard links approach `--depth 1` doesn’t make sense for some reason? And `file://` forces git to copy all objects instead, which is actually slower.
|
|||
|
|
|||
|
### bonus: now my builds are faster than they used to be!
|
|||
|
|
|||
|
One nice thing about this is that my build/deploy time is less than it was on Netlify. For `jvns.ca` it’s about 7 seconds to build and deploy the site instead of about a minute previously.
|
|||
|
|
|||
|
### running the builds on my laptop seems nice
|
|||
|
|
|||
|
I’m the only person who develops all of my sites, so doing all the builds in a Docker container on my computer seems to make sense. My computer is pretty fast and all the files are already right there! No giant downloads! And doing it in a Docker container keeps the build isolated.
|
|||
|
|
|||
|
### example build scripts
|
|||
|
|
|||
|
Here are the build scripts for this blog (`jvns.ca`).
|
|||
|
|
|||
|
**Dockerfile**
|
|||
|
|
|||
|
```
|
|||
|
|
|||
|
FROM ubuntu:20.04
|
|||
|
|
|||
|
RUN apt-get update && apt-get install -y git
|
|||
|
RUN apt-get install -y wget python2
|
|||
|
RUN wget https://github.com/gohugoio/hugo/releases/download/v0.40.1/hugo_0.40.1_Linux-64bit.tar.gz
|
|||
|
RUN wget https://github.com/sass/dart-sass/releases/download/1.49.0/dart-sass-1.49.0-linux-x64.tar.gz
|
|||
|
RUN tar -xf dart-sass-1.49.0-linux-x64.tar.gz
|
|||
|
RUN tar -xf hugo_0.40.1_Linux-64bit.tar.gz
|
|||
|
RUN mv hugo /usr/bin/hugo
|
|||
|
RUN mv dart-sass/sass /usr/bin/sass
|
|||
|
|
|||
|
```
|
|||
|
|
|||
|
**build-docker.sh**:
|
|||
|
|
|||
|
```
|
|||
|
|
|||
|
set -eu
|
|||
|
scripts/parse_titles.py
|
|||
|
sass sass/:static/stylesheets/
|
|||
|
hugo
|
|||
|
|
|||
|
```
|
|||
|
|
|||
|
**deploy.sh**:
|
|||
|
|
|||
|
```
|
|||
|
|
|||
|
set -eu
|
|||
|
tinybuild -s scripts/build-docker.sh \
|
|||
|
-l "$PWD/deploy" \
|
|||
|
-c /build/public
|
|||
|
|
|||
|
rsync-showdiff ./deploy/ [email protected]:/var/www/jvns.ca
|
|||
|
rm -rf ./deploy
|
|||
|
|
|||
|
```
|
|||
|
|
|||
|
### problem 2: getting rsync to just show me which files it updated
|
|||
|
|
|||
|
When I started using rsync to sync the files, it would list every single file instead of just files that had changed. I think this was because I was generating new files for every build, so the timestamps were always newer than the files on the server.
|
|||
|
|
|||
|
I did a bunch of Googling and figured out this incantation to get rsync to just show me files that were updated;
|
|||
|
|
|||
|
```
|
|||
|
|
|||
|
rsync -avc --out-format='%n' "[email protected]" | grep --line-buffered -v '/$'
|
|||
|
|
|||
|
```
|
|||
|
|
|||
|
I put that in a script called `rsync-showdiff` so I could reuse it. There might be a better way, but this seems to work.
|
|||
|
|
|||
|
### problem 3: configuration management
|
|||
|
|
|||
|
All I needed to do to set up the server was:
|
|||
|
|
|||
|
* install nginx
|
|||
|
* create directories in /var/www for each site, like `/var/www/jvns.ca`
|
|||
|
* create an nginx configuration for each site, like `/etc/nginx/sites-enabled/jvns.ca.conf`
|
|||
|
* deploy the files (with my deploy script above)
|
|||
|
|
|||
|
|
|||
|
|
|||
|
I wanted to use some kind of configuration management to do this because that’s how I’m used to managing servers. I’ve used Puppet a lot in the past at work, but I don’t really _like_ using Puppet. So I decided to use Ansible even though I’d never used it before because it seemed simpler than using Puppet. Here’s [my current Ansible configuration][4], minus some of the templates it depends on.
|
|||
|
|
|||
|
I didn’t use any Ansible plugins because I wanted to maximize the probability that I would actually be able to run this thing in 3 years.
|
|||
|
|
|||
|
The most complicated thing in there is probably the `reload nginx` handler, which makes sure that the configuration is still valid after I make an nginx configuration update.
|
|||
|
|
|||
|
### problem 4: replacing a lambda function
|
|||
|
|
|||
|
I was using one Netlify lambda function to calculate purchasing power parity (“PPP”) for countries that have a weaker currency relative to the US on <https://wizardzines.com>. Basically it gets your country using IP geolocation and then returns a discount code if you’re in a country that has a discount code. (like 70% off for India, for example). So I needed to replace it.
|
|||
|
|
|||
|
I handled this by rewriting the (very small) program in Go, copying the static binary to the server, and adding a `proxy_pass` for that site.
|
|||
|
|
|||
|
The program just looks up the country code from the [geolocation HTTP header][5] in a hashmap, so it doesn’t seem like it should cause maintenance problems.
|
|||
|
|
|||
|
### a very simple nginx config
|
|||
|
|
|||
|
I used the same nginx config file for templates for almost all my sites:
|
|||
|
|
|||
|
```
|
|||
|
|
|||
|
server {
|
|||
|
listen 80;
|
|||
|
listen [::]:80;
|
|||
|
|
|||
|
root /var/www/{{item.dir}};
|
|||
|
index index.html index.htm;
|
|||
|
server_name {{item.server}};
|
|||
|
|
|||
|
location / {
|
|||
|
# First attempt to serve request as file, then
|
|||
|
# as directory, then fall back to displaying a 404.
|
|||
|
try_files $uri $uri/ =404;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
```
|
|||
|
|
|||
|
The `{{item.dir}}` is an Ansible thing.
|
|||
|
|
|||
|
I also added support for custom 404 pages (`error_page /404.html`) in the main `nginx.conf`.
|
|||
|
|
|||
|
I’ll probably add TLS support with certbot later. My CDN handles TLS to the client, I just need to make the connection between the CDN and the origin server use TLS
|
|||
|
|
|||
|
Also I don’t know if there are problems with using such a simple nginx config. Maybe I’ll learn about them!
|
|||
|
|
|||
|
### bonus: I can find 404s more easily
|
|||
|
|
|||
|
Another nice bonus of this setup is that it’s easier to see what’s happening with my site – I can just look at the nginx logs!
|
|||
|
|
|||
|
I ran `grep 404 /var/log/nginx/access.log` to figure out if I’d broken anything during the migration, and I actually ended up finding a lot of links that had been broken for many years, but that I’d just never noticed.
|
|||
|
|
|||
|
Netlify’s analytics has a “Top resources not found” that shows you the most common 404s, but I don’t think there’s any way to see _all_ 404s.
|
|||
|
|
|||
|
### a small factor: costs
|
|||
|
|
|||
|
Part of my motivation for this switch was – I was getting close to the Netlify free tier’s bandwidth limit (100GB/month), and Netlify charges $20/100GB for additional bandwidth. Digital Ocean charges $1/100GB for additional bandwidth (20x less), and my droplet comes with 1TB of bandwidth. So the bandwidth pricing feels a lot more reasonable to me.
|
|||
|
|
|||
|
### we’ll see how it goes!
|
|||
|
|
|||
|
All my static sites are running on my own server now. I don’t really know what this will be like to maintain, we’ll see how it goes – maybe I’ll like it! maybe I’ll hate it! I definitely like the faster build times and that I can easily look at my nginx logs.
|
|||
|
|
|||
|
--------------------------------------------------------------------------------
|
|||
|
|
|||
|
via: https://jvns.ca/blog/2022/01/24/hosting-my-static-sites-with-nginx/
|
|||
|
|
|||
|
作者:[Julia Evans][a]
|
|||
|
选题:[lujun9972][b]
|
|||
|
译者:[译者ID](https://github.com/译者ID)
|
|||
|
校对:[校对者ID](https://github.com/校对者ID)
|
|||
|
|
|||
|
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
|||
|
|
|||
|
[a]: https://jvns.ca/
|
|||
|
[b]: https://github.com/lujun9972
|
|||
|
[1]: https://blog.wesleyac.com/posts/how-i-run-my-servers
|
|||
|
[2]: https://wizardzines.com
|
|||
|
[3]: https://github.com/jvns/tinybuild/
|
|||
|
[4]: https://gist.github.com/jvns/06754e9e65b49dd461fefa071dd4aace
|
|||
|
[5]: https://support.cloudflare.com/hc/en-us/articles/200168236-Configuring-Cloudflare-IP-Geolocation
|