3 min read

3rd migration complete ✌ . New era, new Docker stack

3rd migration complete ✌ . New era, new Docker stack

After 12 year I'm still here and, finally I did it, the 3rd big self hosted migration✌.

VPS stats

Nope, my VPS isn't so busy. Just generated some traffic to make it look cooler.

When I started the word "blog" didn't exist yet and internet was a mix of forums and websites, now it's full of social, videos and many people stopped to self host "things" because they prefere something that just works™ that they don't have to mantain. The funny fact it's has never been years to self host a web software as today!


Linux, DNS, reverse proxy, HTTPS, PHP, NodeJS, CMS, VPN, ssh, to self host even a stupid kitten blog, you have to wire a lot of tech, most technologies are open source but you have to know what you are doing and it's your responsibility to check that they continue talk fine even after upgrades. ..And it's not easy to follow any possible stack permutation!

OK, I've a landing page, how a deploy can be easier?

Let's say you have a new web page in ./app/index.html and you want to publish it on on the Internet.. to https://make-self-hosting-great-again.grigio.org what do you do?
It can be easy as using a docker-compose.yml file and run a command!

# docker-compose.yml 

version: "3"
    image: busybox
    restart: unless-stopped
      - ./app:/var/www    
      - web
      - traefik.docker.network=web
      - traefik.frontend.rule=Host:make-self-hosting-great-again.grigio.org
      - traefik.port=9000
      - traefik.backend=msgh
    entrypoint: busybox httpd -f -p -h /var/www
    external: true

And then

docker-compose up -d

And we are live! https://make-self-hosting-great-again.grigio.org



  1. Check with dig subdomain.yourdomain.com that the DNS propagation is complete, it could take some minutes. You should see the name resolved your IP. With A * YOUR.VPS.IP You can resolve all subdomains.

  2. Sometimes the HTTPS / Traefik / Letsencrypt certificate is not valid, you have to wait or try an anonymous session in the browser.. because it isn't updated on every request, at least on Chrome.

  3. The network web is used by Traefik to automagically generate the HTTPS certificate, you could also use a network to share a database name resolution among multiple containers.

Making things simple is hard

To be honest, I tried several times in the past to do this migration, and I failed. At the time docker was immature to my needs but now it's another story. Here is a list of things of my pains:

  • Ideally docker containers are stateless, but some projects, specially old CMS mix app, config, plugins, web server extensions, database configs (ex. mod_rewrite for clean urls,..). There isn't a perfect solution, you can always mount that blob and use the container to freeze the stack.

  • Projects with not clear dependencies are pain. I had problems with old versions of Drupal 4 /6 and Meteor 1.0.3.x Stacks evolves and also Databases and you have to replicate the exact version to port them.

  • mysqldump is a pain, it did a backup of a database with a corrupt table without errors.. but the dump ignored halt of the tables -_-

Thanks to the open source and docker ecosystem

Here some docker images I use:

Base infrastructure

  • Reverse Proxy: Traefik traefik:alpine It is the only gateway to manage websites traffic and HTTPS certs renewal (port 80/443)
  • Container management web UI: Portainer portainer/portainer To have a visual feedback of docker and docker-compose commands
  • System performance overview: Netdata netdata/netdata

Extra infrastructure

  • Email aliases martinpesek/postfix-forwarding
  • VPN server kylemanna/openvpn

App infrastructure

  • Databases: mysql:5.7
  • Stacks: php:5-fpm, ghost:0
  • .. and more but with weird customizations


Some beautyful oneliners..

It creates a temporary mysql container to extract a database backup

docker run --net web -v $PWD/backup:/backup --rm  -i mysql:5.7 sh -c "MYSQL_PWD=yourpassword mysqldump -h mysqlinstance -u yourdbuser yourdatabase | gzip -9 > /backup/dbbackup.sql.gz"

It attaches to mysql container to restore a database backup

docker exec -i mysqlinstance /usr/bin/mysql -u root --password=yourrootpassword -e "create database yourdatabase"
gunzip < backup/dbbackup.sql.gz | docker exec -i mysqlinstance /usr/bin/mysql -u yourdbuser --password=yourpassword yourdatabase

I hope you like it, the cloud isn't just Amazon AWS and Google.