Add post on static sites with traefik
This commit is contained in:
		
							
								
								
									
										171
									
								
								content/post/static-sites-with-mash.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										171
									
								
								content/post/static-sites-with-mash.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,171 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					title: "Hosting static sites with Traefik"
 | 
				
			||||||
 | 
					date: 2023-07-16T15:10:10+02:00
 | 
				
			||||||
 | 
					draft: false
 | 
				
			||||||
 | 
					image: "/uploads/static-sites-with-traefik/html.png"
 | 
				
			||||||
 | 
					categrories: ['English']
 | 
				
			||||||
 | 
					tags: ['MASH', 'hugo', 'english', 'ansible', 'traefik', 'SWS']
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Hosting Static Sites with [Traefik](https://traefik.io/) and [Static Web Server](https://static-web-server.net/features/docker/#run-a-container)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Traefik is amazing to host complex services like with containers. On the other hand it's harder than you'd think to host a simple static html site. I wanted to share my current approach that is based on [Static Web Server Project](https://static-web-server.net/features/docker/#run-a-container).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Static Web Server (SWS)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Static Web Server (or SWS abbreviated) is a simple and really fast web server with the goal to serve static web files or assets. The tiny docker image is only 4 MB with a small memory footprint. We can therefore afford to run a container for each static site.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Architecture
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					On the server we set up all static sites in one folder called `static-sites`. As we run the SWS with [docker-compose](https://docs.docker.com/compose/) we add the `docker-compose.yml` to this folder too. The following is an example setup with two static sites on seperate domains.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					| static-sites
 | 
				
			||||||
 | 
					|
 | 
				
			||||||
 | 
					| docker-compose.yml
 | 
				
			||||||
 | 
					| - domain1.example.org
 | 
				
			||||||
 | 
					   | - index.html
 | 
				
			||||||
 | 
					| - domain2.example.org
 | 
				
			||||||
 | 
					   | - index.html
 | 
				
			||||||
 | 
					   | - fonts
 | 
				
			||||||
 | 
					      | - Open-Dyslexic.odf
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					For each domain we add a folder. I like to name them by the domain name but this is not necessary (just remember the volume in the `docker-compose.yml` too).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					With this done we can now fill the appropriate information in the `docker-compose.yml`. Copy the following and replace `domain1.example.org`, `domain2.example.org`, `site-one` and `site-two`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```yaml
 | 
				
			||||||
 | 
					version: "3.3"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					services:
 | 
				
			||||||
 | 
					  domain1.example.org:
 | 
				
			||||||
 | 
					    image: joseluisq/static-web-server:2
 | 
				
			||||||
 | 
					    container_name: "domain1.example.org"
 | 
				
			||||||
 | 
					    environment:
 | 
				
			||||||
 | 
					      # Note: those envs are customizable but also optional
 | 
				
			||||||
 | 
					      - SERVER_PORT=8080
 | 
				
			||||||
 | 
					      - SERVER_ROOT=/public
 | 
				
			||||||
 | 
					      - SERVER_LOG_LEVEL=info
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - ./domain1.example.org:/public
 | 
				
			||||||
 | 
					    labels:
 | 
				
			||||||
 | 
					      - "traefik.enable=true"
 | 
				
			||||||
 | 
					      - "traefik.docker.network=traefik"
 | 
				
			||||||
 | 
					      - "traefik.http.routers.site-one.rule=Host(`domain1.example.org`)"
 | 
				
			||||||
 | 
					      - "traefik.http.routers.site-one.service=site-one"
 | 
				
			||||||
 | 
					      - "traefik.http.routers.site-one.entrypoints=web-secure"
 | 
				
			||||||
 | 
					      - "traefik.http.routers.site-one.tls=true"
 | 
				
			||||||
 | 
					      - "traefik.http.routers.site-one.tls.certResolver=default"
 | 
				
			||||||
 | 
					      - "traefik.http.services.site-one.loadbalancer.server.port=8080"
 | 
				
			||||||
 | 
					    networks:
 | 
				
			||||||
 | 
					      - traefik
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  domain2.example.org:
 | 
				
			||||||
 | 
					    image: joseluisq/static-web-server:2
 | 
				
			||||||
 | 
					    container_name: "domain2.example.org"
 | 
				
			||||||
 | 
					    environment:
 | 
				
			||||||
 | 
					      # Note: those envs are customizable but also optional
 | 
				
			||||||
 | 
					      - SERVER_PORT=8080
 | 
				
			||||||
 | 
					      - SERVER_ROOT=/public
 | 
				
			||||||
 | 
					      - SERVER_LOG_LEVEL=info
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - ./domain2.example.org:/public
 | 
				
			||||||
 | 
					    labels:
 | 
				
			||||||
 | 
					      - "traefik.enable=true"
 | 
				
			||||||
 | 
					      - "traefik.docker.network=traefik"
 | 
				
			||||||
 | 
					      - "traefik.http.routers.site-two.rule=Host(`domain2.example.org`)"
 | 
				
			||||||
 | 
					      - "traefik.http.routers.site-two.service=site-two"
 | 
				
			||||||
 | 
					      - "traefik.http.routers.site-two.entrypoints=web-secure"
 | 
				
			||||||
 | 
					      - "traefik.http.routers.site-two.tls=true"
 | 
				
			||||||
 | 
					      - "traefik.http.routers.site-two.tls.certResolver=default"
 | 
				
			||||||
 | 
					      - "traefik.http.services.site-two.loadbalancer.server.port=8080"
 | 
				
			||||||
 | 
					    networks:
 | 
				
			||||||
 | 
					      - traefik
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					networks:
 | 
				
			||||||
 | 
					  traefik:
 | 
				
			||||||
 | 
					    name: traefik
 | 
				
			||||||
 | 
					    external: true
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This assumes traefik runs in a docker-network called traefik. This network must already exist.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					As a last step add at least a `index.html` in the appropriate folder. Then you can start the webserver with `docker-compose up`. Add `-d` to run it in the background.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Deploying static sites
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Deploying files manually (via Filezilla, scp or rsync) is not something I like to do. I therefore normally set up a CI job to automatically deploy the site when I push a new commit to GitHub, either via GitHub Actions or my [Woodpecker CI](https://woodpecker-ci.org/docs/usage/intro) instance.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					I order to do that I
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* create a new user on the server, specifically for that purpose (one per site). The command is `useradd USERNAME -m`
 | 
				
			||||||
 | 
					* create a SSH key without a password `ssh-keygen -t ed25519 -a 100 -C "COMMENT" -f FILENAME`
 | 
				
			||||||
 | 
					* copy the public key that was just created at `FILENAME.pub` on the server in the textfile `/home/USERNAME/.ssh/authorized_keys`
 | 
				
			||||||
 | 
					* Add the private key to the secrets of your CI
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					A typical CI configuration will look like this with a static site and GitHub Actions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```yaml
 | 
				
			||||||
 | 
					name: Deploy Production Website via SSH
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					on:
 | 
				
			||||||
 | 
					  push:
 | 
				
			||||||
 | 
					    branches: [main]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					jobs:
 | 
				
			||||||
 | 
					  build:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					      - uses: actions/checkout@v1
 | 
				
			||||||
 | 
					      - name: Deploy to Server
 | 
				
			||||||
 | 
					        uses: easingthemes/ssh-deploy@main
 | 
				
			||||||
 | 
					        env:
 | 
				
			||||||
 | 
					          SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
 | 
				
			||||||
 | 
					          ARGS: "-rltgoDzvO --delete"
 | 
				
			||||||
 | 
					          SOURCE: ""
 | 
				
			||||||
 | 
					          REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
 | 
				
			||||||
 | 
					          REMOTE_USER: ${{ secrets.REMOTE_USER }}
 | 
				
			||||||
 | 
					          TARGET: ${{ secrets.REMOTE_PRODUCTION_TARGET }}
 | 
				
			||||||
 | 
					          EXCLUDE: ".github/, .gitignore"
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					or this, when using [HUGO](https://gohugo.io/) and Woodpecker CI
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```yaml
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pipeline:
 | 
				
			||||||
 | 
					  build:
 | 
				
			||||||
 | 
					    image: klakegg/hugo 
 | 
				
			||||||
 | 
					    commands:
 | 
				
			||||||
 | 
					      - hugo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  deploy:
 | 
				
			||||||
 | 
					    image: appleboy/drone-scp
 | 
				
			||||||
 | 
					    settings:
 | 
				
			||||||
 | 
					      strip_components: 1
 | 
				
			||||||
 | 
					      host:
 | 
				
			||||||
 | 
					        - example.org
 | 
				
			||||||
 | 
					      username: moanos
 | 
				
			||||||
 | 
					      target: /home/USERNAME/static-sites/
 | 
				
			||||||
 | 
					      source: public/
 | 
				
			||||||
 | 
					      key:
 | 
				
			||||||
 | 
					        from_secret: ssh_key
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					not manually put the files on the server
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								static/uploads/static-sites-with-traefik/html.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								static/uploads/static-sites-with-traefik/html.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 39 KiB  | 
		Reference in New Issue
	
	Block a user