Deploying Astro with Docker Compose and Traefik
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. ↩

Fontsource, Fontaine, Tailwind and Vite
Using Fontsource together with Fontaine to self-host fonts with fallbacks in Astro or SvelteKit, integrating with Tailwind, compared to font preloading.

Painless PhotoSwipe Lightboxes for Astro MDX Blog Posts
How to integrate PhotoSwipe with Astro's MDX content and image optimization pipelines for a performant, painless image gallery experience in Astro blog posts.

astro & away we go!
Astro is a relatively new frontend framework that's particularly well-suited to building a developer blog.
Slashing URLs with Astro and Nginx
Default Astro and Nginx behaviors redirect to URLs with trailing slashes, which is great for SEO and user experience.
Add diagrams to your Astro site with D2
A guide to adding flowcharts and diagrams to your Astro site using D2, a text-to-diagram tool. Tips on docker build and clutter control.
