When I created my blog app, I decided to use PM2 to manage the node application that runs my blog. This created a few small problems. First my blog is a Node application, which PM2 handles quite well, but my other applications are a mixture of Rust binaries, Java jars, and Node applications. Additionally, my blog relied on git to pull some data from a git repository, which was triggered by a shell script. This meant that I had to manage PM2 processes for node, systemd processes for Rust and Java and Nginx, cron jobs for shell scripts, I had to figure out my development environment and copy files to the server in a convenient way. Suffice it to say this was all becoming something of a mess. The problem came to the forefront when I had an issue with PM2 where I had neglected to restart it after an update, which caused the running PM2 processes and the version of PM2 on the server to be out of sync, which, I assume, was the cause of the PM2 processes continually using virtually my entire CPU. Once I had realized the issue and queried PM2 about how its day was going, it was an easy issue to fix, but it was sort of the straw that broke the camel’s back on getting me to reorganize how my server was managed.
Predictably, I decided to just dockerize everything. There wasn’t anything super interesting here aside from how I set up my cron job within the containers. Previously, I had installed git on my server, and a shell script that lived in my filesystem would do a git pull and copy the relevant files to where they needed to go. Instead, I decided that it would be better to have this managed in a separate image and push it to Docker Hub separately. This way I could make small changes to it without affecting the main application, which could function independently. First, I created a simple shell script:
cd /usr/src/repo-code git pull https://repos-r-us.com/some-repo.git mkdir -p /usr/app/static/json cp /usr/src/repo-code/websites.json /usr/app/static/json/
For the Dockerfile
, I use an Alpine image and then install git, which is not included in the image, clone the repo, and create the cron job. The approach is more portable and appears to work well. I believe cron is installed on this Alpine image, so there is no need to install it independently.
FROM alpine:3.14 RUN apk add --no-cache git WORKDIR /usr/src RUN git clone https://repos-r-us.com/some-repo.git COPY ./copy-json.sh /usr/scripts/ RUN chmod +x /usr/scripts/copy-json.sh RUN mkdir /usr/websites RUN /usr/scripts/copy-repo.sh RUN echo "0 4 * * * /usr/scripts/copy-repo.sh" > /etc/crontabs/root CMD ["crond", "-f"]
There were several other small optimizations. I minified the CSS with clean-css, a node library for minifying CSS. Most of the clean-css documentation is devoted to writing a JavaScript application that uses clean-css as a library to minify the CSS, but it also provides a second node CLI application, and the same basic functionality can be achieved with a simple bash script. Clean-css has a long list of options and optimizations, but for a simple application, a lot of it feels overkill. This approach should be portable because the dependency is in the Bun application itself and, conveniently, Bun seems to detect that the dependency is in your project and you don’t need to install it globally or give it an absolute path. Additionally, I realized that I did not defer HTMX in the script tag, which was blocking my HTML from loading and other small optimizations.
Moving forward, I’ve considered setting up Watchtower or something similar to manage the image updates, but for now I am happy with how everything works. In terms of content, I am hoping to start writing a lot more posts moving forward. Next up, I am going to do a short post on NUTS, my new C project.
Messing around with computers and coding since I was 8. Now getting paid to do what I love.