#### Overview |Feature|kubectl port-forward|SSH Tunnel (-L)|VPN (Wireguard/Tailscale)| |---|---|---|----| |Setup|Zero (if you have kubectl)|Requires SSH access|Requires Client/Driver install| |Scope|Single Port/Service|Single Port/Service|Entire Network| |Lifetime|Temporary (dies if cmd closes)|Temporary|Persistent/Background| |OS Level|User-space (Application)|User-space (Application)|Kernel-space (Network Card)| From a technical point of view, kubectl port-forward is most accurately described as a User-Space Application Proxy or a Layer 7 Tunnel. It is not a full-blown VPN or a network-level tunnel like Wireguard. Instead, it is a "targeted bridge" created specifically for one-way traffic between your local machine and a specific service inside the cluster. 1. Closest Match: SSH Local Port Forwarding (ssh -L) This is the "twin brother" of kubectl port-forward. How it's similar: You use an existing encrypted management channel (SSH or the Kubernetes API) to "piggyback" traffic for a specific port. Neither creates a new network interface on your machine; they just listen on a local port and "teleport" data to the other side. The Comparison: Just as ssh -L 5436:localhost:5432 user@server maps a remote DB to your laptop, kubectl does the same for a Pod. 2. Cloudflare Tunnel (cloudflared) How it's similar: Both allow you to bypass firewalls and NAT without opening public ports. They both rely on an "outbound" connection from the cluster/server to a central controller to establish the path. The Difference: Cloudflare Tunnels are designed for permanent, public-facing access; port-forward is a temporary, private developer tool. 3. What it is NOT: VPNs (OpenVPN, Wireguard, Tailscale) Why they are different: A VPN creates a Virtual Network Interface (like tun0 or utun). When you use a VPN, your laptop actually "lives" inside the 10.x.x.x network. You could ping any Pod or Service directly by its internal IP. With port-forward: Your laptop doesn't know the db namespace exists. It only knows that localhost:5436 is magically connected to something else. #### Where do they live on the OSI Model? ``` 1. Layer 7 (Application Layer): kubectl port-forward and SSH Tunneling Reason: These tools do not understand "IP addresses" or "Routing" in the traditional sense. When you run kubectl port-forward, the kubectl binary on your machine opens a local socket. It takes the data from that socket, wraps it in an HTTP/SPDY or HTTP/2 request, and sends it to the Kubernetes API server. The API server then unwraps that data and sends it to the Pod. Because the "tunneling" happens inside an application protocol (HTTP/SSH), it is a Layer 7 operation. Layer 3 (Network Layer): VPNs (Wireguard, OpenVPN, IPSec) Reason: These create a Virtual Network Interface (like utun0). They handle IP Packets. If you send a ping to 10.42.0.5, the VPN intercepts that packet at the Network Layer, encrypts it, and sends it to the other side. It doesn't care if the data inside is Postgres, HTTP, or DNS; it only cares about the IP Header. Layer 2 (Data Link Layer): VXLAN, Tinc, or Bridge-mode VPNs Reason: Some advanced VPNs can operate at Layer 2. They transport Ethernet Frames (including MAC addresses). This makes your local computer think it is plugged into the same "virtual switch" as the remote servers. 2. Other Technologies to Connect If port-forward is too temporary, here are the professional alternatives: A. Ingress Controller (Layer 7) How it works: You define a "Route" (e.g., db.example.com). The Ingress (like Nginx or Traefik) receives the traffic and forwards it to your service. Layer: 7 (Application). It looks at the Hostname and Path in the HTTP/TLS header to decide where to send the data. B. LoadBalancer Service (Layer 4) How it works: In a cloud environment (AWS/GCP), this gives your Service a real, reachable IP address. Layer: 4 (Transport). It only cares about the Port and Protocol (TCP/UDP). It doesn't look at the data inside the packets. Comparison: This is like a "Permanent Port-Forward" that anyone on the internet (or your VPC) can reach. C. Mesh VPNs / SD-WAN (Layer 3 - Overlay) Examples: Tailscale, ZeroTier, Nebula. How it works: You install a small agent on your VM and your laptop. They create a "Peer-to-Peer" encrypted mesh. Layer: 3 (Network). It provides a stable IP address for your database that stays the same even if you move from office to home. Reason to use: This is the most "production-ready" version of what you are doing. It is more secure than a public LoadBalancer but more stable than a port-forward. ``` |Technology|OSI Layer|Comparison| |----|---|---| |kubectl port-forward|Layer 7|"A temporary ""straw"" to sip data from one |specific cup.| |Ingress|Layer 7|A ""Receptionist"" directing visitors based on the name on the |envelope.| |LoadBalancer|Layer 4|"A ""Direct Pipeline"" to a specific door (port).| |Tailscale / VPN|Layer 3|"A ""Secret Tunnel"" that puts your whole house next to the office.| #### Finding IP public: curl ifconfig.me |Target|Command|Requirement| |----|----|-----| |Inside Container| hostname -I /ip addr show eth0|LXC Container| |LXC Local IP|pct exec ID ip a|Access to Proxmox Host| |Proxmox Local IP|hostname -I|Access to Proxmox Host| |Proxmox Public IP|curl ifconfig.me|Internet Access| #### Ipaymu Integration need static IP [Cloudflare Worker] ──1. Request──► [Your Nginx Server] ──2. Request──► [iPaymu API] │ │ [Cloudflare Worker] ◄─4. Response── [Your Nginx Server] ◄─3. Response─── [iPaymu API] Problem: we deploy using cloudflared worker which has dynamic ips - Find IP of the server - curl -4 ifconfig.me - curl -4 icanhazip.com - curl -4 ipinfo.io/ip - ip -4 addr show 1. Option 1: Cloudflare Tunnel (cloudflared) — Easiest & Most Secure A. cloudflared inside an LXC Container (Separate from K3s) An LXC container on the same LAN/Proxmox host can run cloudflared and route incoming traffic directly to your K3s cluster. [Cloudflare Worker] │ (Public HTTPS) ▼ [Cloudflare Edge Network] │ (Outbound Tunnel) [LXC Container running cloudflared] │ (Local LAN / Bridge Traffic) [K3s Cluster Node (Static IP)] ──► [Nginx Proxy Pod] ──► [iPaymu API] How to configure the LXC Tunnel: When you set up the Public Hostname rule in the Cloudflare Zero Trust Dashboard (or config.yaml), point the origin URL directly to your K3s Node IP and port: - Service Type: HTTP - URL: : (e.g., [http://192.168.1.50:80](http://192.168.1.50:80) if calling Traefik/Ingress, or [http://192.168.1.50:30080](http://192.168.1.50:30080) if using a NodePort service). Pros & Cons: - Pros: Keeps your K3s cluster lightweight; if K3s reboots, the tunnel daemon stays alive in the LXC container. - Cons: Requires exposing a NodePort or LoadBalancer (Traefik) on your K3s node so the LXC container can hit it on the local network. B. cloudflared as a Pod inside K3s Deploying cloudflared directly inside K3s as a Deployment is the most common Kubernetes-native method. [Cloudflare Worker] │ ▼ (HTTPS over public internet) [https://proxy.yourdomain.com] <-- Cloudflare Edge │ ▼ (Secure outbound Tunnel) [cloudflared Pod in K3s / Swarm] │ ▼ (Internal Cluster Traffic) [Nginx Proxy Pod (Static-IP Node)] │ ▼ (Outbound call from Static IPv4) [iPaymu API] 2. Option 2: Traefik Ingress Controller + NodePort / Public IP If your static-IP cluster node already has a Public Static IPv4 Address directly bound to its network interface: [Cloudflare Worker] │ ▼ (HTTPS to Public Static IP) [Public IP:443] ──► [Traefik Ingress] ──► [Nginx Proxy Service] ──► [iPaymu] How to configure the K3s Tunnel: Inside the Cloudflare dashboard, you point the public hostname to K3s's internal DNS service name: - Service Type: HTTP - URL: ipaymu-proxy-service.default.svc.cluster.local:80 Pros & Cons: - Pros: Highly secure—you do not need to open any ports or expose NodePort services on your LAN. Traffic moves entirely within K3s cluster networking. - Cons: Runs inside the Kubernetes runtime lifecycle. 3. Option 3: Port Mapping directly on the Static Node (hostPort) If you don't want to deal with Traefik or Ingress controllers, you can expose your Nginx proxy directly on a port (e.g., 8080 or 8443) of your static-IP node using hostPort in K3s or ports mapping in Swarm. K3s Manifest Snippet: containers: - name: nginx image: nginx:alpine ports: - containerPort: 80 hostPort: 8080 # Exposes port 8080 directly on the physical host Vour Cloudflare Worker calls http://:8080/ipaymu/api/v2/payment. - How the Request Flow Works End-to-End Once exposed via Option 1, 2, or 3, here is the full round-trip execution path: ``` // 1. Cloudflare Worker executes this code: const response = await fetch("https://proxy.yourdomain.com/ipaymu/api/v2/payment", { method: "POST", headers: { "X-Proxy-Secret": "YOUR_INTERNAL_SECRET", "va": "1176000000000000", "signature": "CALCULATED_HMAC_SIGNATURE", "Content-Type": "application/json" }, body: JSON.stringify(paymentPayload) }); // 2. Request reaches https://proxy.yourdomain.com (Your Cluster via Tunnel/Ingress) // 3. Ingress routes it internally to your Nginx proxy pod // 4. Nginx verifies "X-Proxy-Secret", then forwards the request to https://api.ipaymu.com // 5. iPaymu receives the call, sees your Node's STATIC IP, and accepts it // 6. iPaymu returns payment data -> Nginx -> Ingress -> Cloudflare Worker -> Client ``` #### Step by step using ONLY Traefik in K3s ``` Step 1: Create Traefik Middlewares for Security and Stripping Instead of Nginx if ($http_x_proxy_secret) directives, you define Traefik Middleware resources: # 1. Security Header Check Middleware apiVersion: traefik.io/v1alpha1 kind: Middleware metadata: name: ipaymu-auth-check namespace: default spec: headers: customRequestHeaders: # Ensures requests forwarded to iPaymu clean up internal proxy secrets X-Proxy-Secret: "" --- # 2. Path Strip Middleware (Removes /ipaymu prefix before forwarding) apiVersion: traefik.io/v1alpha1 kind: Middleware metadata: name: ipaymu-strip-prefix namespace: default spec: stripPrefix: prefixes: - /ipaymu Step 2: Define an ExternalName Service for iPaymu To tell Traefik to forward traffic out to iPaymu's external domain, create a Kubernetes ExternalName service: apiVersion: v1 kind: Service metadata: name: ipaymu-external-api namespace: default spec: type: ExternalName externalName: api.ipaymu.com ports: - port: 443 targetPort: 443 protocol: TCP Step 3: Route Traffic with Traefik IngressRoute Now, create a Traefik IngressRoute pinned to your static IP node: apiVersion: traefik.io/v1alpha1 kind: IngressRoute metadata: name: ipaymu-proxy-route namespace: default spec: entryPoints: - web - websecure routes: - match: Host(`proxy.yourdomain.com`) && PathPrefix(`/ipaymu`) kind: Rule middlewares: - name: ipaymu-strip-prefix services: - name: ipaymu-external-api port: 443 scheme: https When should you STILL use Nginx? While Traefik replaces Nginx for 90% of use cases, keeping a tiny Nginx pod is still useful if: Complex Logic: You want custom Nginx Lua scripts or complex header manipulation (e.g., computing dynamic HMAC signatures or custom retry logic on upstream failures). Familiarity: You are already comfortable writing .conf files and don't want to learn Traefik-specific Kubernetes CRD syntax (IngressRoute / Middleware). ``` #### Step by step using traeffik and nginx in k3s ``` 1. Label your target node (Crucial Step) Label the K3s node that holds your static public IPv4 address so K3s knows where to schedule the proxy pods: kubectl label nodes egress-ip=static-ipaymu 2. k3s-ipaymu-proxy.yaml apiVersion: v1 kind: ConfigMap metadata: name: ipaymu-proxy-config namespace: default data: default.conf: | upstream ipaymu_backend { server api.ipaymu.com:443; keepalive 32; } server { listen 80; location /ipaymu/ { if ($http_x_proxy_secret != "YOUR_INTERNAL_SECRET_KEY") { return 403; } proxy_pass https://ipaymu_backend/; proxy_set_header Host api.ipaymu.com; # SSL Handshake settings proxy_ssl_server_name on; proxy_ssl_protocols TLSv1.2 TLSv1.3; # HTTP Keepalive proxy_http_version 1.1; proxy_set_header Connection ""; } } --- apiVersion: apps/v1 kind: Deployment metadata: name: ipaymu-proxy namespace: default spec: replicas: 2 # Scale up replicas as needed selector: matchLabels: app: ipaymu-proxy template: metadata: labels: app: ipaymu-proxy spec: # Pin all replicas to the node with the static public IP nodeSelector: egress-ip: static-ipaymu containers: - name: nginx image: nginx:alpine ports: - containerPort: 80 resources: limits: cpu: "200m" memory: "128Mi" requests: cpu: "50m" memory: "32Mi" volumeMounts: - name: nginx-config mountPath: /etc/nginx/conf.d/default.conf subPath: default.conf volumes: - name: nginx-config configMap: name: ipaymu-proxy-config --- apiVersion: v1 kind: Service metadata: name: ipaymu-proxy-service namespace: default spec: type: ClusterIP ports: - port: 80 targetPort: 80 protocol: TCP selector: app: ipaymu-proxy 3. Deploy : kubectl apply -f k3s-ipaymu-proxy.yaml 4. How to call it inside K3s Any other pod inside your K3s cluster can now make payment requests directly using internal Kubernetes DNS: [http://ipaymu-proxy-service.default.svc.cluster.local/ipaymu/api/v2/payment](http://ipaymu-proxy-service.default.svc.cluster.local/ipaymu/api/v2/payment) ``` #### Step by step using traeffik and cloudflared in k3s ``` Step 2.1: Enable allowExternalNameServices in Traefik By default, K3s disables Traefik from routing to external domains via ExternalName. Create a HelmChartConfig manifest on your K3s server node to enable it: Create file /var/lib/rancher/k3s/server/manifests/traefik-config.yaml: apiVersion: helm.cattle.io/v1 kind: HelmChartConfig metadata: name: traefik namespace: kube-system spec: valuesContent: |- providers: kubernetesCRD: allowExternalNameServices: true K3s automatically applies this change in under 30 seconds. Step 2.2: Label your Static-IP Node Label the node with your static public IPv4 address so K3s knows where to schedule the tunnel: kubectl label nodes egress-ip=static-ipaymu Step 2.3: Deploy the iPaymu Traefik Route Save this to ipaymu-traefik.yaml and run kubectl apply -f ipaymu-traefik.yaml: # 1. External Name Service pointing to iPaymu apiVersion: v1 kind: Service metadata: name: ipaymu-external-service namespace: default spec: type: ExternalName externalName: api.ipaymu.com ports: - name: https port: 443 targetPort: 443 protocol: TCP --- # 2. TLS Transport for SNI support apiVersion: traefik.io/v1alpha1 kind: ServersTransport metadata: name: ipaymu-transport namespace: default spec: serverName: api.ipaymu.com --- # 3. Path Stripper Middleware (/ipaymu/api/v2/payment -> /api/v2/payment) apiVersion: traefik.io/v1alpha1 kind: Middleware metadata: name: ipaymu-strip-prefix namespace: default spec: stripPrefix: prefixes: - /ipaymu --- # 4. Traefik IngressRoute apiVersion: traefik.io/v1alpha1 kind: IngressRoute metadata: name: ipaymu-proxy-route namespace: default spec: entryPoints: - web - websecure routes: - match: Host(`proxy.yourdomain.com`) && PathPrefix(`/ipaymu`) kind: Rule middlewares: - name: ipaymu-strip-prefix services: - name: ipaymu-external-service port: 443 scheme: https serversTransport: ipaymu-transport Step 2.4: - Get Your Tunnel Secret Token 1. Log into your Cloudflare Zero Trust Dashboard. 2. Navigate to Networks → Tunnels → Create a Tunnel. 3. Choose Cloudflared as the connector and name it (e.g., k3s-ipaymu-tunnel). 4. On the installation page, select Docker/Kubernetes. Look for the command and copy the long base64 token string after --token. - Deploy Cloudflare Tunnel inside K3s Save this to cloudflared.yaml (replace YOUR_TOKEN_HERE with your Cloudflare Zero Trust tunnel token) and run kubectl apply -f cloudflared.yaml: apiVersion: v1 kind: Secret metadata: name: cloudflared-token namespace: default type: Opaque stringData: TUNNEL_TOKEN: "YOUR_TOKEN_HERE" --- apiVersion: apps/v1 kind: Deployment metadata: name: cloudflared namespace: default spec: replicas: 1 selector: matchLabels: app: cloudflared template: metadata: labels: app: cloudflared spec: nodeSelector: egress-ip: static-ipaymu containers: - name: cloudflared image: cloudflare/cloudflared:latest args: - tunnel - --no-autoupdate - run env: - name: TUNNEL_TOKEN valueFrom: secretKeyRef: name: cloudflared-token key: TUNNEL_TOKEN kubectl apply -f cloudflared.yaml Step 2.5: Cloudflare Dashboard Routing In Cloudflare Zero Trust Dashboard (→ Networks → Tunnels → Public Hostname): Public Hostname: proxy.yourdomain.com Path: /ipaymu Type: HTTP URL: traefik.kube-system.svc.cluster.local:80 ``` #### Step by step using nginx and cloudflared in docker-compose ``` Step 3.1: Create Directory Structure On your target server, create a project directory: mkdir -p ipaymu-proxy cd ipaymu-proxy ipaymu-proxy/ ├── docker-compose.yml └── nginx.conf Step 3.2: Create nginx.conf events { worker_connections 1024; } http { upstream ipaymu_api { server api.ipaymu.com:443; keepalive 16; } server { listen 80; server_name _; location /ipaymu/ { # Check secret key header sent by your Cloudflare Worker if ($http_x_proxy_secret != "YOUR_INTERNAL_SECRET_KEY") { return 403; } proxy_pass https://ipaymu_api/; proxy_set_header Host api.ipaymu.com; # Ensure TLS handshake works with iPaymu proxy_ssl_server_name on; proxy_ssl_protocols TLSv1.2 TLSv1.3; # HTTP Keepalive optimizations proxy_http_version 1.1; proxy_set_header Connection ""; } } } Step 3.3: Create docker-compose.yml version: '3.8' services: ipaymu-proxy: image: nginx:alpine container_name: ipaymu_proxy restart: always ports: - "80:80" # Or expose via reverse proxy / Cloudflare tunnel volumes: - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro healthcheck: test: ["CMD", "nginx", "-t"] interval: 30s timeout: 10s retries: 3 or with nginx: #docker-compose.yml version: '3.8' services: ipaymu-nginx: image: nginx:alpine container_name: ipaymu_nginx restart: always volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro networks: - proxy-net cloudflared: image: cloudflare/cloudflared:latest container_name: ipaymu_tunnel restart: always command: tunnel --no-autoupdate run environment: - TUNNEL_TOKEN=YOUR_CLOUDFLARE_TUNNEL_TOKEN_HERE networks: - proxy-net networks: proxy-net: driver: bridge Step 3.4: Deploy Docker Compose: docker compose up -d Step 3.5: Cloudflare Dashboard RoutingIn Cloudflare Zero Trust Dashboard ($\rightarrow$ Networks $\rightarrow$ Tunnels $\rightarrow$ Public Hostname): Public Hostname: proxy.yourdomain.com Path: /ipaymuType: HTTP URL: ipaymu_nginx:80 (Connects directly using Docker's internal container DNS). ``` #### Step by step using docker swarm ``` version: '3.8' services: ipaymu-proxy: image: nginx:alpine ports: - "80:80" - "443:443" configs: - source: nginx_config target: /etc/nginx/conf.d/default.conf deploy: mode: replicated replicas: 1 placement: constraints: # Pin execution to the specific node with the static public IP - node.hostname == node-with-static-ip configs: nginx_config: inline: | server { listen 80; server_name ipaymu-proxy.yourdomain.com; location /ipaymu/ { if ($http_x_proxy_secret != "YOUR_INTERNAL_SECRET") { return 403; } proxy_pass https://api.ipaymu.com/; proxy_set_header Host api.ipaymu.com; proxy_ssl_server_name on; proxy_ssl_protocols TLSv1.2 TLSv1.3; # Pass client details proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } [Cloudflare Worker] │ ├─── /ipaymu/v2/payment ─────────► [Nginx] ──► https://my.ipaymu.com/api/v2/payment │ └─── /ipaymu-sandbox/v2/payment ─► [Nginx] ──► https://sandbox.ipaymu.com/api/v2/payment 1.a Create the dedicated folder in /opt/ mkdir -p /opt/ipaymu-swarm-proxy cd /opt/ipaymu-swarm-proxy b create the nginx configuration file nginx.conf: ``` events { worker_connections 1024; } http { # Upstream targets using persistent HTTP keepalives upstream ipaymu_live { server my.ipaymu.com:443; keepalive 16; } upstream ipaymu_sandbox { server sandbox.ipaymu.com:443; keepalive 16; } server { listen 80; server_name _; # ------------------------------------------------------------- # 1. Production / Live Endpoint # ------------------------------------------------------------- location /ipaymu/v2/ { # Security Header Check if ($http_x_proxy_secret != "YOUR_INTERNAL_SECRET_KEY") { return 403; } proxy_pass https://ipaymu_live/api/v2/; proxy_set_header Host my.ipaymu.com; # Ensure TLS/SNI Handshake matches my.ipaymu.com proxy_ssl_server_name on; proxy_ssl_name my.ipaymu.com; proxy_ssl_protocols TLSv1.2 TLSv1.3; # Connection optimizations proxy_http_version 1.1; proxy_set_header Connection ""; proxy_pass_request_headers on; } # ------------------------------------------------------------- # 2. Development / Sandbox Endpoint # ------------------------------------------------------------- location /ipaymu-sandbox/v2/ { # Security Header Check if ($http_x_proxy_secret != "YOUR_INTERNAL_SECRET_KEY") { return 403; } proxy_pass https://ipaymu_sandbox/api/v2/; proxy_set_header Host sandbox.ipaymu.com; # Ensure TLS/SNI Handshake matches sandbox.ipaymu.com proxy_ssl_server_name on; proxy_ssl_name sandbox.ipaymu.com; proxy_ssl_protocols TLSv1.2 TLSv1.3; # Connection optimizations proxy_http_version 1.1; proxy_set_header Connection ""; proxy_pass_request_headers on; } } } ``` 2. Change ipaymu ``` # erstelle einen starker schlüssel YOUR_INTERNAL_SECRET_KEY: openssl rand -hex 32 # Setze diesen Schlüssel in deinen Nginx-Code (nginx.conf): if ($http_x_proxy_secret != "0fb1d9712f16bb64e0dff4f911af02e8a2280722c80097a845e248c7a49781d7") { return 403; } Setze denselben Schlüssel in deinen Cloudflare Worker / App-Code: const headers = { "Content-Type": "application/json", "X-Proxy-Secret": "0fb1d9712f16bb64e0dff4f911af02e8a2280722c80097a845e248c7a49781d7", // Gleicher Schlüssel! // ... }; CONTAINER_ID=$(docker ps -q -f name=ipaymu_nginx-proxy | head -n1) docker exec -it $CONTAINER_ID apk add --no-cache curl docker exec -it $CONTAINER_ID curl -4 ifconfig.me curl -i -X POST https://proxy.yourdomain.com/ipaymu/v2/payment \ -H "X-Proxy-Secret: YOUR_INTERNAL_SECRET_KEY" \ -H "Content-Type: application/json" ``` 1. Das Label auf den gewählten Node setzen Sobald du weißt, welcher Node die statische IP hat (z. B. node-01), vergibst du das Label egress=static-ipaymu: ``` docker node update --label-add egress=static-ipaymu node-01 docker node update --label-add egress=static-ipaymu docker node inspect node-01 --format '{{ json .Spec.Labels }}' ``` 4. Deploy And check docker run cloudflare/cloudflared:latest tunnel --no-autoupdate run --token eyJhIjoiNjFhMTZkYzg3MmM4ZjlmZTQ4Mzc0ZWQ5NzVmNTcwNmIiLCJ0IjoiNTA5ZmRlZjQtZjEzYy00ZTQ1LTllZDQtYjZhMjExOTFlY2Y3IiwicyI6Ik5UWmtZVFU1T1dZdE5XVmtZUzAwTldZeExXSm1Oek10TlRRd1pqUTBNR1kxWW1RMSJ9 - Option 1 Using Docker Swarm env ( does not work ) ``` version: '3.8' services: nginx-proxy: image: nginx:alpine configs: - source: nginx_config_v1 target: /etc/nginx/nginx.conf networks: - payment-network deploy: mode: replicated replicas: 2 placement: constraints: # Pin to the node holding your static IP for iPaymu whitelisting - node.labels.egress == static-ipaymu restart_policy: condition: on-failure cloudflared: image: cloudflare/cloudflared:latest command: tunnel --no-autoupdate run environment: - TUNNEL_TOKEN=${CLOUDFLARE_TUNNEL_TOKEN} networks: - payment-network deploy: mode: replicated replicas: 2 placement: constraints: - node.labels.egress == static-ipaymu restart_policy: condition: on-failure configs: nginx_config_v1: file: ./nginx.conf networks: payment-network: driver: overlay attachable: true ``` create .env file in the same directory: sudo nano /opt/ipaymu-swarm-proxy/.env CLOUDFLARE_TUNNEL_TOKEN=eyJhIjoiNjFhMTZkYzg3MmM4ZjlmZTQ4Mzc0ZWQ5NzVmNTcwNmIiLCJ0IjoiNTA5ZmRlZjQtZjEzYy00ZTQ1LTllZDQtYjZhMjExOTFlY2Y3IiwicyI6Ik5UWmtZVFU1T1dZdE5XVmtZUzAwTldZeExXSm1Oek10TlRRd1pqUTBNR1kxWW1RMSJ9 docker run cloudflare/cloudflared:latest tunnel --no-autoupdate run --token eyJhIjoiNjFhMTZkYzg3MmM4ZjlmZTQ4Mzc0ZWQ5NzVmNTcwNmIiLCJ0IjoiNTA5ZmRlZjQtZjEzYy00ZTQ1LTllZDQtYjZhMjExOTFlY2Y3IiwicyI6Ik5UWmtZVFU1T1dZdE5XVmtZUzAwTldZeExXSm1Oek10TlRRd1pqUTBNR1kxWW1RMSJ9 docker stack deploy -c ipaymu-swarm.yml ipaymu or export $(cat /opt/ipaymu-swarm-proxy/.env | xargs) && sudo -E docker stack deploy -c ipaymu-swarm.yml ipaymu or set -a && source /opt/ipaymu-swarm-proxy/.env && set +a && sudo -E docker stack deploy -c ipaymu-swarm.yml ipaymu or CLOUDFLARE_TUNNEL_TOKEN="eyJhIjoiNjFhMTZkYzg3MmM4ZjlmZTQ4Mzc0ZWQ5NzVmNTcwNmIiLCJ0IjoiNTA5ZmRlZjQtZjEzYy00ZTQ1LTllZDQtYjZhMjExOTFlY2Y3IiwicyI6Ik5UWmtZVFU1T1dZdE5XVmtZUzAwTldZeExXSm1Oek10TlRRd1pqUTBNR1kxWW1RMSJ9" sudo -E docker stack deploy -c ipaymu-swarm.yml ipaymu # Option 2 Docker Swarm Secret Create a Docker Secret on your Manager node: printf "your_actual_token_here" | docker secret create cloudflared_tunnel_token - version: '3.8' services: cloudflared: image: cloudflare/cloudflared:latest # Tells cloudflared to read the token file created by Docker Secrets command: tunnel --no-autoupdate run --token-file /run/secrets/cloudflared_token secrets: - source: cloudflared_tunnel_token target: cloudflared_token networks: - ipaymu-overlay deploy: mode: replicated replicas: 2 placement: constraints: - node.labels.egress == static-ipaymu secrets: cloudflared_tunnel_token: external: true Summary Recommendation For simple setups: Use Method 1 (.env file). For strict security requirements: Use Method 4 (Docker Secrets). docker stack services ipaymu docker service logs ipaymu_cloudflared --tail 20 Now you need to label the node like in docker-stack.yml: docker node update --label-add egress=static-ipaymu docker node update --label-add egress=static-ipaymu invixel-debian1 docker node update --label-add egress=static-ipaymu invixel-vm5 How to verify the exit IP After deploying to K3s or Swarm, exec into your Nginx container/pod and test what public IP iPaymu will see: docker exec -it curl -4 ifconfig.me kubectl exec -it deployment/ipaymu-proxy -- curl -4 ifconfig.me curl -i -X POST https://bali-car-dev.easy-rent.com/ipaymu/v2/payment \ -H "X-Proxy-Secret: 0fb1d9712f16bb64e0dff4f911af02e8a2280722c80097a845e248c7a49781d7" \ -H "Content-Type: application/json" ```