All checks were successful
Blog Deployment / Check-Rebuild (push) Successful in 7s
Blog Deployment / Build (push) Has been skipped
Blog Deployment / Deploy-Staging (push) Successful in 11s
Blog Deployment / Test-Staging (push) Successful in 3s
Blog Deployment / Merge (push) Successful in 8s
Blog Deployment / Deploy-Production (push) Successful in 12s
Blog Deployment / Test-Production (push) Successful in 3s
Blog Deployment / Clean (push) Has been skipped
Blog Deployment / Notify (push) Successful in 3s
114 lines
5.1 KiB
Markdown
114 lines
5.1 KiB
Markdown
---
|
||
slug:
|
||
title: Template
|
||
description:
|
||
date:
|
||
draft: true
|
||
tags:
|
||
categories:
|
||
---
|
||
## Intro
|
||
|
||
In this post, I am not going to explain best practices for deploying applications. Instead, I want to document how I am currently deploying new applications in my homelab.
|
||
|
||
Think of this article as a snapshot in time. This is how things really work today, knowing that in the near future I would like to move toward a more GitOps-oriented workflow.
|
||
|
||
The method I use is fairly simple. I have tried to standardize it as much as possible, but it still involves quite a few manual steps. I will also explain how I update applications, which is, in my opinion, the biggest weakness of this setup. As the number of applications keeps growing, keeping everything up to date requires more and more time.
|
||
|
||
---
|
||
## Platform Overview
|
||
|
||
Before diving into the workflow, here is a quick overview of the main components involved.
|
||
### Docker
|
||
|
||
Docker is the foundation of my application stack. Whenever possible, I deploy applications as containers.
|
||
|
||
I have been using Docker Compose for years. At the time, everything was running on a single physical server. Today, my setup is VM-based, and I could migrate to Docker Swarm, but I have chosen not to. It might make sense in some scenarios, but it is not aligned with where I want to go long term.
|
||
|
||
For now, I still rely on a single VM to host all Docker applications. This VM is more or less a clone of my old physical server, just virtualized.
|
||
|
||
### Proxmox
|
||
|
||
All my VMs are hosted on a Proxmox cluster.
|
||
|
||
The cluster is composed of three nodes and uses Ceph as a distributed storage backend. This gives me high availability and makes VM management much easier, even though the Docker workloads themselves are not highly distributed.
|
||
|
||
### Traefik
|
||
|
||
Traefik runs directly on the Docker host and acts as the reverse proxy.
|
||
|
||
It is responsible for routing HTTP and HTTPS traffic to the correct containers and for managing TLS certificates automatically using Let’s Encrypt. This keeps application-level configuration simple and centralized.
|
||
|
||
### OPNsense
|
||
@Explain briefly OPNsense
|
||
|
||
Incoming HTTPS traffic is forwarded to Traefik using the Caddy plugin with Layer 4 rules. TLS is not terminated at the firewall level. It is passed through to Traefik, which handles certificate issuance and renewal.
|
||
|
||
### Gitea
|
||
|
||
I host a Gitea server in my homelab.
|
||
|
||
Inside Gitea, I have a private repository that contains all my Docker Compose configurations. Each application has its own folder, making the repository easy to navigate and maintain.
|
||
|
||
## Deploy New Application
|
||
|
||
To standardize deployments, I use a `docker-compose.yml` template that looks like this:
|
||
```yml
|
||
services:
|
||
NAME:
|
||
image: IMAGE
|
||
container_name: NAME
|
||
volumes:
|
||
- /appli/data/NAME/:/
|
||
environment:
|
||
- TZ=Europe/Paris
|
||
networks:
|
||
- web
|
||
labels:
|
||
- traefik.enable=true
|
||
- traefik.http.routers.NAME.rule=Host(`HOST.vezpi.com`)
|
||
- traefik.http.routers.NAME.entrypoints=https
|
||
- traefik.http.routers.NAME.tls.certresolver=letsencrypt
|
||
- traefik.http.services.NAME.loadbalancer.server.port=PORT
|
||
restart: always
|
||
|
||
networks:
|
||
web:
|
||
external: true
|
||
```
|
||
|
||
Let me explain.
|
||
|
||
For the image, depending on the application, the registry used could differ, but I still the Docker Hub by default. When I try a new application, I might use the `latest` at start. Then if I choose to keep the application, I prefer to pin the version instead of `latest`.
|
||
|
||
|
||
Steps to deploy a new application:
|
||
From VScode:
|
||
- I create a new folder in that repository
|
||
- I copy the template file inside this folder
|
||
- I adapt the template with the values given by the application documentation
|
||
- I try to avoid using the latest tag for the images
|
||
- Eventually I create a .env file to store secrets which is ignored by the .gitignore of the repo
|
||
- If volumes are needed, I use bind mounts on a specific FS on the server
|
||
- I run the services directly from VScode using a Docker extension
|
||
From OPNsense
|
||
- In the Caddy plugin, I update 2 Layer4 routes:
|
||
- Depending if the application should be exposed on the internet or not, I have an Internal or External route. I add the URL given to Traefik in one of these.
|
||
- I also add this URL in another route to redirect the HTTP challenge to Traefik
|
||
|
||
Finally I test the URL and it should work!
|
||
Once everything work as expected, I commit the new folder on the repo
|
||
## Update Application
|
||
|
||
Updating my applications is still manual to me. I don't use tools like Watchtower for now. Every month or so, I check for new versions. I check on the Docker hub, GitHub or on the application documentation.
|
||
|
||
For each of the application I want to uppdate, I look for new features, breaking changes and try to bump them to the latest version.
|
||
|
||
Most of the time, updating an application is straightforward. I update the image tag and restart the docker compose stack. Then I verify if the application restart properly, check the docker logs and test the application to detect any regression.
|
||
|
||
If the tests are successful I continue to update until I reach the latest version available. Once reached, I commit the update in the repository.
|
||
|
||
|
||
## Conclusion
|
||
|
||
Using Docker |