🔧 AI Nachrichten Major AI platforms go down in unprecedented simultaneous outage(03.09.2026 um 17:34 Uhr)
🔧 AI Nachrichten ChatGPT, Claude, and Grok Down? Users Report Widespread Outages(03.09.2026 um 19:14 Uhr)
🔧 AI Nachrichten OpenAI Launches GPT-6 Astra, Says We May Have Entered the AGI Era(03.09.2026 um 22:08 Uhr)
🔧 AI Nachrichten Claude Comes to CarPlay as Fifth Major AI Chatbot App(05.09.2026 um 05:31 Uhr)
🔧 AI Nachrichten OpenAI’s GPT-6 Astra Is AGI, Says NVIDIA CEO Jensen Huang(07.09.2026 um 06:31 Uhr)
🔧 AI Nachrichten Blame AI companies for Mac mini and Mac Studio shortage(31.08.2026 um 10:32 Uhr)
🔧 AI Nachrichten Major AI platforms go down in unprecedented simultaneous outage(03.09.2026 um 17:34 Uhr)
🔧 AI Nachrichten ChatGPT, Claude, and Grok Down? Users Report Widespread Outages(03.09.2026 um 19:14 Uhr)
🔧 AI Nachrichten OpenAI Launches GPT-6 Astra, Says We May Have Entered the AGI Era(03.09.2026 um 22:08 Uhr)
🔧 AI Nachrichten Claude Comes to CarPlay as Fifth Major AI Chatbot App(05.09.2026 um 05:31 Uhr)
🔧 AI Nachrichten OpenAI’s GPT-6 Astra Is AGI, Says NVIDIA CEO Jensen Huang(07.09.2026 um 06:31 Uhr)
🔧 AI Nachrichten Blame AI companies for Mac mini and Mac Studio shortage(31.08.2026 um 10:32 Uhr)

🔧 Programmierung 🕛 kürzlich 9 Min Lesezeit
0

Self-Hosting Like a Pro, Part 2: Docker, K3s and Traefik v3 with Automatic HTTPS

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

This is Part 2 of a four-part series where I document how I turned a 10€/month VPS into a production-grade platform hosting multiple projects. In Part 1 we hardened a fresh Ubuntu server. Today we install the actual platform: a container runtime, a lightweight Kubernetes distribution, a modern reverse proxy, and automatic TLS certificates. By the end of this article, deploying an app with HTTPS will take one YAML file and zero manual certificate handling.*






Why Kubernetes on a single VPS?



Fair question. Docker Compose would work, and for a single project I would recommend it without hesitation. But my server hosts several unrelated projects: a portfolio blog, a university group website, and a SaaS platform. I want:





  • Isolation: a memory leak in one project must not take down the others.


  • Resource quotas: each project gets a defined slice of the 8 GB RAM, enforced by the platform, not by hope.


  • Declarative deployments: the entire infrastructure lives in Git as YAML. If the server burns down, I can rebuild it in an hour.


  • One ingress to rule them all: a single reverse proxy routes all domains and handles all certificates.



Full Kubernetes (kubeadm) would eat half the server's resources just to run itself. K3s is a certified Kubernetes distribution by SUSE that fits in a ~512 MB memory footprint. Same API, same YAML, a fraction of the overhead. It is the sweet spot for a single-node setup.






What you need




  • The hardened Ubuntu VPS from Part 1 (2 vCPU / 8 GB RAM or more recommended)

  • A domain name with DNS you control

  • 45 minutes



Before starting, create a DNS A record pointing a test subdomain to your server, for example:




CODE
whoami.yourdomain.com  →  YOUR_SERVER_IP






DNS propagation can take a few minutes, so doing it now means it will be ready when we need it in Step 6.






Step 1: Install Docker



K3s uses its own container runtime (containerd), so strictly speaking Docker is not required to run the cluster. We install it anyway, because it is invaluable for building images locally on the server and for quick debugging.



Use the official convenience script:




CODE
curl -fsSL https://get.docker.com | sudo sh






Add your user to the docker group so you can run it without sudo:




CODE
sudo usermod -aG docker $USER
newgrp docker






Verify:




CODE
docker run --rm hello-world






One important detail on a hardened server: Docker manipulates iptables directly and can bypass UFW rules when you publish ports with -p. Since all our public traffic will go through K3s and Traefik, avoid publishing Docker ports on this machine. If you ever need a quick test container, bind it to localhost only: -p 127.0.0.1:8080:80.






Step 2: Install K3s



One command, about thirty seconds:




CODE
curl -sfL https://get.k3s.io | sh -s - \
--disable traefik \
--write-kubeconfig-mode 644






Two flags deserve an explanation:





  • --disable traefik: K3s bundles Traefik, but we want to install and control our own Traefik v3 via Helm, with our own configuration. Letting K3s manage it means fighting its defaults later.


  • --write-kubeconfig-mode 644: makes the kubeconfig readable by non-root users, convenient on a single-user server. On a shared machine, skip this flag and copy the kubeconfig to your user manually.



Check that the node is ready:




CODE
kubectl get nodes






Expected output:




CODE
NAME        STATUS   ROLES                  AGE   VERSION
srv-xxxxx Ready control-plane,master 30s v1.33.x+k3s1






Make kubectl work in every session by pointing it at the K3s kubeconfig:




CODE
echo 'export KUBECONFIG=/etc/rancher/k3s/k3s.yaml' >> ~/.bashrc
source ~/.bashrc









Step 3: Install Helm



Helm is the package manager for Kubernetes. We will use it to install Traefik and cert-manager with pinned, reproducible configurations.




CODE
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
helm version









Step 4: Install Traefik v3



Traefik is our front door: it receives all HTTP/HTTPS traffic on ports 80 and 443, routes requests to the right application based on the domain name, and terminates TLS.



Add the official Helm repository:




CODE
helm repo add traefik https://traefik.github.io/charts
helm repo update






Create a values file so the configuration lives in Git, not in your shell history:




CODE
mkdir -p ~/infra/traefik && nano ~/infra/traefik/values.yaml









CODE
# ~/infra/traefik/values.yaml
deployment:
kind: Deployment
replicas: 1

service:
type: LoadBalancer # K3s ships with ServiceLB (Klipper), this binds ports 80/443 on the node

ports:
web:
port: 8000
exposedPort: 80
redirections:
entryPoint:
to: websecure
scheme: https
permanent: true
websecure:
port: 8443
exposedPort: 443
tls:
enabled: true

providers:
kubernetesCRD:
enabled: true
kubernetesIngress:
enabled: true

logs:
general:
level: INFO
access:
enabled: true

resources:
requests:
cpu: 100m
memory: 128Mi
limits:
memory: 256Mi






The redirections block is doing quiet but important work: every HTTP request is permanently redirected to HTTPS at the entrypoint level, so no application ever needs to think about it.



Install into its own namespace:




CODE
kubectl create namespace traefik
helm install traefik traefik/traefik \
--namespace traefik \
--values ~/infra/traefik/values.yaml






Verify the pod is running and the ports are bound:




CODE
kubectl get pods -n traefik
kubectl get svc -n traefik






The service should show EXTERNAL-IP equal to your server's IP, with ports 80 and 443 mapped. At this point, visiting http://YOUR_SERVER_IP returns a 404 from Traefik — which is perfect: the front door is open, there is just nothing behind it yet.






Step 5: Install cert-manager



cert-manager automates the entire Let's Encrypt lifecycle: it requests certificates, answers the ACME challenges, stores the certs as Kubernetes secrets and renews them before expiry. Set it up once, never think about TLS again.




CODE
helm repo add jetstack https://charts.jetstack.io
helm repo update

helm install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--set crds.enabled=true






Wait for the three pods to be ready:




CODE
kubectl get pods -n cert-manager






Now define two ClusterIssuers: one for Let's Encrypt staging, one for production. Always test with staging first, because the production endpoint rate-limits you to 5 failed attempts per hour, and debugging DNS issues while rate-limited is a special kind of misery.




CODE
mkdir -p ~/infra/cert-manager && nano ~/infra/cert-manager/cluster-issuers.yaml









CODE
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
server: https://acme-staging-v02.api.letsencrypt.org/directory
email: [email protected]
privateKeySecretRef:
name: letsencrypt-staging-key
solvers:
- http01:
ingress:
class: traefik
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: [email protected]
privateKeySecretRef:
name: letsencrypt-prod-key
solvers:
- http01:
ingress:
class: traefik






Replace the email (Let's Encrypt uses it for expiry warnings) and apply:




CODE
kubectl apply -f ~/infra/cert-manager/cluster-issuers.yaml
kubectl get clusterissuers






Both issuers should show READY: True after a few seconds.






Step 6: Deploy a test app with automatic HTTPS



Time for the payoff. We deploy whoami, a tiny web server that echoes request information, and expose it over HTTPS on the subdomain you configured earlier.




CODE
mkdir -p ~/infra/apps && nano ~/infra/apps/whoami.yaml









CODE
apiVersion: v1
kind: Namespace
metadata:
name: ns-test
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: whoami
namespace: ns-test
spec:
replicas: 1
selector:
matchLabels:
app: whoami
template:
metadata:
labels:
app: whoami
spec:
containers:
- name: whoami
image: traefik/whoami:latest
ports:
- containerPort: 80
resources:
requests:
cpu: 10m
memory: 16Mi
limits:
memory: 32Mi
---
apiVersion: v1
kind: Service
metadata:
name: whoami-svc
namespace: ns-test
spec:
selector:
app: whoami
ports:
- port: 80
targetPort: 80
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: whoami-cert
namespace: ns-test
spec:
secretName: whoami-tls
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- whoami.yourdomain.com
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: whoami-route
namespace: ns-test
spec:
entryPoints:
- websecure
routes:
- match: Host(`whoami.yourdomain.com`)
kind: Rule
services:
- name: whoami-svc
port: 80
tls:
secretName: whoami-tls






Replace whoami.yourdomain.com with your actual subdomain (twice), then:




CODE
kubectl apply -f ~/infra/apps/whoami.yaml






Watch the certificate being issued:




CODE
kubectl get certificate -n ns-test -w






Within a minute, READY flips to True. Now open https://whoami.yourdomain.com in your browser: a valid Let's Encrypt certificate, automatic HTTP→HTTPS redirect, and the whoami output showing your request went through Traefik.



Take a second to appreciate what just happened: you wrote one YAML file, and the platform handled deployment, routing, certificate issuance and TLS termination. Every future application follows the exact same pattern.



If the certificate stays stuck in READY: False, the usual suspects are:




CODE
# Check the ACME challenge status
kubectl describe certificaterequest -n ns-test
kubectl get challenges -n ns-test






Nine times out of ten it is DNS: the A record has not propagated yet, or points to the wrong IP. Verify with dig whoami.yourdomain.com +short.






Final checklist






CODE
# 1. K3s node ready
kubectl get nodes
# Expected: STATUS Ready

# 2. Traefik running and bound to 80/443
kubectl get svc -n traefik
# Expected: EXTERNAL-IP = your server IP, ports 80/443

# 3. ClusterIssuers ready
kubectl get clusterissuers
# Expected: both READY True

# 4. Test app serving valid HTTPS
curl -sI https://whoami.yourdomain.com | head -1
# Expected: HTTP/2 200

# 5. HTTP redirects to HTTPS
curl -sI http://whoami.yourdomain.com | grep -i location
# Expected: location: https://whoami.yourdomain.com/






Once everything passes, clean up the test app if you like:




CODE
kubectl delete -f ~/infra/apps/whoami.yaml









What's next



We now have a hardened server running a Kubernetes platform with automatic HTTPS. In Part 3, we make it truly multi-tenant: one namespace per project with enforced resource quotas, a shared PostgreSQL and Redis for the small projects, and Traefik middlewares for security headers, rate limiting and CORS. That is where the "several unrelated projects on one 8 GB server without them stepping on each other" promise gets fulfilled.



Questions or suggestions? Reach out, I answer everything. If your certificate refuses to issue and you have already sacrificed a rubber duck to the DNS gods, send me the output of kubectl get challenges and we will figure it out.

Vollständiger Original-Bericht
Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
3 Quellen
GPT-6 Astra Release Today? OpenAI’s Next Major AI Model Is Almost Here
1 Quelle
Apple accuses OpenAI of destroying evidence as trade-secrets fight intensifies
1 Quelle
Major AI platforms go down in unprecedented simultaneous outage
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Self-Hosting Like a Pro, Part 2: Docker, K3s and Traefik v3 with Automatic HTTPS

Thematisch verwandte Begriffe: SelfHosting, Like, Part, Docker · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...