Deploying Astro with Docker Compose and Traefik
/ 4 min read
In my last post, I outlined how I optimized my Astro blog’s build process to compress static files with brotli compression. This post outlines how I serve my blog from a docker container at aaronjbecker.com using docker compose and traefik, as well as how I deploy it using a bare-bones bash script with SSH agent forwarding.
This post assumes you already have a server running docker compose, as well as a Dockerfile to build and serve your Astro site. I’ll be using my own configuration for this site as an example.
step 1: configure container infrastructure
I use docker compose to build and run the container for this blog, which allows me to attach configuration labels for traefik. Traefik is an open-source tool that makes it possible to host arbitrarily many websites on a single server, with each running as a separate container. Traefik routes incoming requests to the appropriate container and automatically generates and renews SSL certificates so the sites are served over HTTPS1.
Here’s my docker-compose.yml
file, which lives in the blog’s root directory:
# blog container is only exposed on the `traefik` network,# which is created in conjunction with the traefik container.networks: traefik: external: true
services: ajb_blog: container_name: ajb_blog restart: unless-stopped build: context: . networks: - traefik labels: - traefik.enable=true - "traefik.http.routers.ajb_blog.rule=Host(`aaronjbecker.com`) || Host(`www.aaronjbecker.com`)" # redirect http to https (configured in traefik.yml) - "traefik.http.routers.ajb_blog.entrypoints=websecure" # generate and renew HTTPS certificates with traefik - "traefik.http.routers.ajb_blog.tls.certResolver=myResolver" # redirect www to non-www - "traefik.http.routers.ajb_blog.middlewares=redirect-www@file"
step 2: deploy!
First, some background on my codebase: since I’m a solo developer at the moment, I can get away with keeping all of the projects I’m working on in a single git monorepo. Being a solo developer also means that I don’t have much to gain from using a CI/CD pipeline, so I deploy directly from my git repository by manually running bash scripts when I have changes I want to deploy.
Since I don’t want to keep my GitHub SSH key on my server, I use SSH agent forwarding, which allows me to pull changes from my repository on my server using keys stored on my local machine. I use a separate SSH key to connect to my server, which provides some degree of security isolation.
Here’s the bash script I use to deploy my blog, which lives in a directory with other scripts I use to deploy other projects:
#!/bin/bash# Script to deploy blog updates to the server automatically# Usage: ./deployBlog.sh or `bash deployBlog.sh`
# Get current branch nameCURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)echo "Deploying branch: $CURRENT_BRANCH"
# Load SSH keys (git and docker server)ssh-add ~/.ssh/id_rsassh-add ~/.ssh/id_ed25519_ovh
# Remote commands to execute on the serverssh -A SERVER_USERNAME@SERVER_IP_ADDRESS << EOF # navigate to directory with monorepo cd ~/aaronjbecker.com
# fetch and checkout the current branch git fetch git checkout $CURRENT_BRANCH git pull
# rebuild the docker stack for the blog; explicit down is not needed. cd astro_blog docker compose up -d --build --remove-orphansEOF
echo "Deployment complete!"
Some details like my server’s IP address and root username are redacted for obvious reasons. I have the script configured to checkout and pull whichever branch I’m working on locally so that I don’t have to create a pull request to my main branch if I’m just testing something out.
closing thoughts
Although this may mark me as crazy, I genuinely like working with docker, even if it’s overkill for this blog. My setup makes it possible to securely and efficiently host multiple websites on a single low-cost server, with each running as its own docker-compose stack.
Traefik is very good at SSL termination and routing, and docker-compose makes it easy to insert additional components to address gaps in Traefik’s capabilities, like a varnish proxy to handle caching backend responses.
As for bash scripts, they can often be illegible, but that’s less of a problem in this new age of AI-assisted coding. I like that bash scripts work on both of my development machines, a MacBook Pro and an Ubuntu desktop, without any additional setup. And I can take comfort in the fact that shell scripting isn’t going anywhere anytime soon.
Footnotes
I also run the traefik reverse proxy container with docker-compose. Configuration for that is beyond the scope of this post but here’s a tutorial from the traefik team if you’re interested. ↩