@hed-hog/cli 0.0.49 → 0.0.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,217 @@
1
+ # Deployment Guide
2
+
3
+ This project is configured for deployment to **<%= config.provider === 'digitalocean' ? 'Digital Ocean Kubernetes' : config.provider %>** using **<%= config.cicd === 'github-actions' ? 'GitHub Actions' : config.cicd %>**.
4
+
5
+ ## Prerequisites
6
+
7
+ Make sure you have the following tools installed:
8
+
9
+ - `kubectl` - Kubernetes CLI
10
+ - `doctl` - Digital Ocean CLI
11
+ - `gh` - GitHub CLI
12
+ - `helm` - Kubernetes package manager
13
+
14
+ ## Initial Setup
15
+
16
+ ### 1. Configure Digital Ocean
17
+
18
+ ```bash
19
+ # Authenticate with Digital Ocean
20
+ doctl auth init
21
+
22
+ # Get your cluster kubeconfig
23
+ doctl kubernetes cluster kubeconfig save <%= config.clusterName %>
24
+ ```
25
+
26
+ <% if (config.createNamespace) { %>### 2. Create Kubernetes Namespace
27
+
28
+ ```bash
29
+ kubectl create namespace <%= config.namespace %>
30
+ ```
31
+
32
+ <% } else { %>### 2. Verify Namespace
33
+
34
+ ```bash
35
+ # Verify the namespace exists
36
+ kubectl get namespace <%= config.namespace %>
37
+ ```
38
+
39
+ <% } %>### 3. Configure GitHub Secrets
40
+
41
+ Add the following secrets to your GitHub repository:
42
+
43
+ 1. `DIGITALOCEAN_ACCESS_TOKEN` - Your Digital Ocean API token
44
+
45
+ ```bash
46
+ # Get your DO token and add it to GitHub
47
+ gh secret set DIGITALOCEAN_ACCESS_TOKEN
48
+ ```
49
+
50
+ <% if (config.setupSSL) { %>### 4. Install cert-manager
51
+
52
+ ```bash
53
+ # Install cert-manager for SSL certificates
54
+ kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.yaml
55
+
56
+ # Wait for cert-manager to be ready
57
+ kubectl wait --for=condition=ready pod -l app.kubernetes.io/instance=cert-manager -n cert-manager --timeout=300s
58
+
59
+ # Apply the ClusterIssuer
60
+ kubectl apply -f k8s/cert-manager-issuer.yaml
61
+ ```
62
+ <% } %>
63
+ <% if (config.setupIngress) { %>### <%= config.setupSSL ? '5' : '4' %>. Install NGINX Ingress Controller
64
+
65
+ ```bash
66
+ # Install NGINX Ingress Controller
67
+ helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
68
+ helm repo update
69
+
70
+ helm install nginx-ingress ingress-nginx/ingress-nginx \
71
+ --namespace ingress-nginx \
72
+ --create-namespace \
73
+ --set controller.publishService.enabled=true
74
+
75
+ # Wait for the load balancer IP
76
+ kubectl get service nginx-ingress-ingress-nginx-controller -n ingress-nginx --watch
77
+ ```
78
+
79
+ **Important:** After the LoadBalancer gets an external IP, configure your DNS:
80
+
81
+ <% if (config.domain) { %>- Point `<%= config.domain %>` to the LoadBalancer IP
82
+ <% if (config.apps.includes('api')) { %>- Point `api.<%= config.domain %>` to the LoadBalancer IP
83
+ <% } %><% } %>
84
+ <% } %>
85
+ ## Deployment
86
+
87
+ ### Using GitHub Actions (Automatic)
88
+
89
+ Push to the `<%= config.deployBranch || 'production' %>` branch:
90
+
91
+ ```bash
92
+ git add .
93
+ git commit -m "Deploy to production"
94
+ git push origin <%= config.deployBranch || 'production' %>
95
+ ```
96
+
97
+ The GitHub Actions workflow will automatically:
98
+ 1. Apply cluster configuration (namespace, Kubernetes manifests, and Helm charts)
99
+ 2. Build and push application Docker images
100
+ 3. Update deployments and wait for rollout
101
+
102
+ ### Manual Deployment
103
+
104
+ #### Option 1: Apply cluster config manually (same as workflow)
105
+
106
+ ```bash
107
+ kubectl create namespace <%= config.namespace %> --dry-run=client -o yaml | kubectl apply -f -
108
+ <% if (config.setupSSL) { %>kubectl apply -f k8s/cert-manager-issuer.yaml
109
+ <% } %><% config.apps.forEach((app) => { %>kubectl apply -f k8s/<%= app %>/ -n <%= config.namespace %>
110
+ <% }); %><% if (config.setupIngress) { %>kubectl apply -f k8s/ingress.yaml -n <%= config.namespace %>
111
+ <% } %>```
112
+
113
+ <% if ((config.infraServices || []).length > 0) { %>#### Additional infrastructure services (generated from <%= config.composeFileName || 'docker-compose' %>)
114
+
115
+ The CLI generated Helm charts for the selected services under `helm/services` using the same image names and versions from compose.
116
+
117
+ Run the following commands to create these services in your cluster:
118
+
119
+ ```bash
120
+ <% (config.infraServices || []).forEach((service) => { %>helm upgrade --install <%= config.appName %>-<%= service.name %> ./helm/services/<%= service.name %> --namespace <%= config.namespace %> --create-namespace
121
+ <% }); %>```
122
+ <% } %>
123
+
124
+ #### Option 2: Build and deploy application images
125
+
126
+ ```bash
127
+ <% if (config.apps.includes('api')) { %>docker build -t <%= config.containerRegistry %>/<%= config.appName %>-api:latest -f apps/api/Dockerfile .
128
+ docker push <%= config.containerRegistry %>/<%= config.appName %>-api:latest
129
+ kubectl set image deployment/<%= config.appName %>-api <%= config.appName %>-api=<%= config.containerRegistry %>/<%= config.appName %>-api:latest -n <%= config.namespace %>
130
+ kubectl rollout status deployment/<%= config.appName %>-api -n <%= config.namespace %>
131
+
132
+ <% } %><% if (config.apps.includes('admin')) { %>docker build -t <%= config.containerRegistry %>/<%= config.appName %>-admin:latest \
133
+ --build-arg NEXT_PUBLIC_API_BASE_URL=<%= config.domain ? `https://api.${config.domain}` : '' %> \
134
+ --build-arg NEXT_PUBLIC_API_URL=<%= config.domain ? `https://api.${config.domain}` : '' %> \
135
+ -f apps/admin/Dockerfile .
136
+ docker push <%= config.containerRegistry %>/<%= config.appName %>-admin:latest
137
+ kubectl set image deployment/<%= config.appName %>-admin <%= config.appName %>-admin=<%= config.containerRegistry %>/<%= config.appName %>-admin:latest -n <%= config.namespace %>
138
+ kubectl rollout status deployment/<%= config.appName %>-admin -n <%= config.namespace %>
139
+ <% } %><% (config.apps || []).filter((app) => app !== 'api' && app !== 'admin').forEach((app) => { %>
140
+ docker build -t <%= config.containerRegistry %>/<%= config.appName %>-<%= app %>:latest -f apps/<%= app %>/Dockerfile .
141
+ docker push <%= config.containerRegistry %>/<%= config.appName %>-<%= app %>:latest
142
+ kubectl set image deployment/<%= config.appName %>-<%= app %> <%= config.appName %>-<%= app %>=<%= config.containerRegistry %>/<%= config.appName %>-<%= app %>:latest -n <%= config.namespace %>
143
+ kubectl rollout status deployment/<%= config.appName %>-<%= app %> -n <%= config.namespace %>
144
+ <% }); %>```
145
+
146
+ ## Monitoring
147
+
148
+ ### Check Deployment Status
149
+
150
+ ```bash
151
+ # Check pods
152
+ kubectl get pods -n <%= config.namespace %>
153
+
154
+ # Check deployments
155
+ kubectl get deployments -n <%= config.namespace %>
156
+
157
+ # Check services
158
+ kubectl get services -n <%= config.namespace %>
159
+
160
+ <% if (config.setupIngress) { %># Check ingress
161
+ kubectl get ingress -n <%= config.namespace %>
162
+ <% } %># View logs
163
+ <% config.apps.forEach((app) => { %>kubectl logs -f deployment/<%= config.appName %>-<%= app %> -n <%= config.namespace %>
164
+ <% }); %>```
165
+
166
+ ### Scaling
167
+
168
+ ```bash
169
+ # Scale a deployment
170
+ <% config.apps.forEach((app) => { %>kubectl scale deployment/<%= config.appName %>-<%= app %> --replicas=3 -n <%= config.namespace %>
171
+ <% }); %>```
172
+
173
+ ## Rollback
174
+
175
+ ```bash
176
+ # View rollout history
177
+ <% config.apps.forEach((app) => { %>kubectl rollout history deployment/<%= config.appName %>-<%= app %> -n <%= config.namespace %>
178
+ <% }); %>
179
+ # Rollback to previous version
180
+ <% config.apps.forEach((app) => { %>kubectl rollout undo deployment/<%= config.appName %>-<%= app %> -n <%= config.namespace %>
181
+ <% }); %>```
182
+
183
+ ## Troubleshooting
184
+
185
+ ### View Pod Events
186
+
187
+ ```bash
188
+ kubectl describe pod <pod-name> -n <%= config.namespace %>
189
+ ```
190
+
191
+ ### View Cluster Events
192
+
193
+ ```bash
194
+ kubectl get events -n <%= config.namespace %> --sort-by='.lastTimestamp'
195
+ ```
196
+
197
+ ### Access Pod Shell
198
+
199
+ ```bash
200
+ <% config.apps.forEach((app) => { %>kubectl exec -it deployment/<%= config.appName %>-<%= app %> -n <%= config.namespace %> -- /bin/sh
201
+ <% }); %>```
202
+
203
+ ## URLs
204
+
205
+ <% if (config.domain) { %>- **Admin Panel:** https://<%= config.domain %>
206
+ <% if (config.apps.includes('api')) { %>- **API:** https://api.<%= config.domain %>
207
+ <% } %><% } else { %>- Configure your domain and update DNS records as described above
208
+ <% } %>
209
+
210
+ ## Further Reading
211
+
212
+ - [Digital Ocean Kubernetes Documentation](https://docs.digitalocean.com/products/kubernetes/)
213
+ - [GitHub Actions Documentation](https://docs.github.com/en/actions)
214
+ - [Kubernetes Documentation](https://kubernetes.io/docs/home/)
215
+ - [Helm Documentation](https://helm.sh/docs/)
216
+ <% if (config.setupSSL) { %>- [cert-manager Documentation](https://cert-manager.io/docs/)
217
+ <% } %>
@@ -0,0 +1,6 @@
1
+ apiVersion: v2
2
+ name: <%= service.name %>
3
+ description: Helm chart for <%= service.name %> service (generated from docker-compose)
4
+ type: application
5
+ version: 0.1.0
6
+ appVersion: "1.0.0"
@@ -0,0 +1,20 @@
1
+ apiVersion: apps/v1
2
+ kind: Deployment
3
+ metadata:
4
+ name: {{ .Values.serviceName }}
5
+ namespace: {{ .Values.namespace }}
6
+ spec:
7
+ replicas: {{ .Values.replicas }}
8
+ selector:
9
+ matchLabels:
10
+ app: {{ .Values.serviceName }}
11
+ template:
12
+ metadata:
13
+ labels:
14
+ app: {{ .Values.serviceName }}
15
+ spec:
16
+ containers:
17
+ - name: {{ .Values.serviceName }}
18
+ image: {{ .Values.image }}
19
+ ports:
20
+ - containerPort: {{ .Values.service.port }}
@@ -0,0 +1,14 @@
1
+ apiVersion: v1
2
+ kind: Service
3
+ metadata:
4
+ name: {{ .Values.serviceName }}
5
+ namespace: {{ .Values.namespace }}
6
+ spec:
7
+ type: {{ .Values.service.type }}
8
+ selector:
9
+ app: {{ .Values.serviceName }}
10
+ ports:
11
+ - name: tcp
12
+ protocol: TCP
13
+ port: {{ .Values.service.port }}
14
+ targetPort: {{ .Values.service.port }}
@@ -0,0 +1,7 @@
1
+ namespace: <%= config.namespace %>
2
+ serviceName: <%= service.name %>
3
+ image: <%= service.image %>
4
+ replicas: 1
5
+ service:
6
+ type: ClusterIP
7
+ port: <%= servicePort %>
@@ -0,0 +1,14 @@
1
+ apiVersion: cert-manager.io/v1
2
+ kind: ClusterIssuer
3
+ metadata:
4
+ name: letsencrypt-prod
5
+ spec:
6
+ acme:
7
+ server: https://acme-v02.api.letsencrypt.org/directory
8
+ email: <%= config.email %>
9
+ privateKeySecretRef:
10
+ name: letsencrypt-prod
11
+ solvers:
12
+ - http01:
13
+ ingress:
14
+ class: nginx
@@ -0,0 +1,14 @@
1
+ apiVersion: apps/v1 kind: Deployment metadata: name: <%= config.appName %>-<%=
2
+ app %> namespace: <%= config.namespace %> labels: app: <%= config.appName %>-<%=
3
+ app %> spec: replicas: <%= config.appReplicas?.[app] ?? 2 %> selector:
4
+ matchLabels: app: <%= config.appName %>-<%= app %> template: metadata: labels:
5
+ app: <%= config.appName %>-<%= app %> spec: containers: - name: <%=
6
+ config.appName %>-<%= app %> image: <%= config.containerRegistry %>/<%=
7
+ config.appName %>-<%= app %>:latest ports: - containerPort: <%=
8
+ config.appPorts?.[app] ?? (app === 'api' ? 3000 : 80) %> env: - name: NODE_ENV
9
+ value: "production" resources: requests: memory: "256Mi" cpu: "100m" limits:
10
+ memory: "512Mi" cpu: "500m" livenessProbe: httpGet: path: <%= app === 'api' ?
11
+ '/health' : '/' %> port: <%= config.appPorts?.[app] ?? (app === 'api' ? 3000 :
12
+ 80) %> initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path:
13
+ <%= app === 'api' ? '/health' : '/' %> port: <%= config.appPorts?.[app] ?? (app
14
+ === 'api' ? 3000 : 80) %> initialDelaySeconds: 10 periodSeconds: 5
@@ -0,0 +1,54 @@
1
+ <%
2
+ var selectedApps = config.apps || [];
3
+ var extraApps = selectedApps.filter(function (app) {
4
+ return app !== 'api' && app !== 'admin';
5
+ });
6
+ var appPorts = config.appPorts || {};
7
+ %>
8
+ apiVersion: networking.k8s.io/v1
9
+ kind: Ingress
10
+ metadata:
11
+ name: <%= config.appName %>-ingress
12
+ namespace: <%= config.namespace %>
13
+ annotations:
14
+ kubernetes.io/ingress.class: "nginx"
15
+ <% if (config.setupSSL) { %> cert-manager.io/cluster-issuer: "letsencrypt-prod"
16
+ <% } %>spec:
17
+ <% if (config.setupSSL) { %> tls:
18
+ - hosts:
19
+ - <%= config.domain %>
20
+ <% if (selectedApps.includes('api')) { %> - api.<%= config.domain %>
21
+ <% } %><% extraApps.forEach(function (app) { %> - <%= app %>.<%= config.domain %>
22
+ <% }); %> secretName: <%= config.appName %>-tls
23
+ <% } %> rules:
24
+ <% if (selectedApps.includes('admin')) { %> - host: <%= config.domain %>
25
+ http:
26
+ paths:
27
+ - path: /
28
+ pathType: Prefix
29
+ backend:
30
+ service:
31
+ name: <%= config.appName %>-admin
32
+ port:
33
+ number: <%= appPorts.admin || 80 %>
34
+ <% } %><% if (selectedApps.includes('api')) { %> - host: api.<%= config.domain %>
35
+ http:
36
+ paths:
37
+ - path: /
38
+ pathType: Prefix
39
+ backend:
40
+ service:
41
+ name: <%= config.appName %>-api
42
+ port:
43
+ number: <%= appPorts.api || 3000 %>
44
+ <% } %><% extraApps.forEach(function (app) { %> - host: <%= app %>.<%= config.domain %>
45
+ http:
46
+ paths:
47
+ - path: /
48
+ pathType: Prefix
49
+ backend:
50
+ service:
51
+ name: <%= config.appName %>-<%= app %>
52
+ port:
53
+ number: <%= appPorts[app] || 80 %>
54
+ <% }); %>
@@ -0,0 +1,6 @@
1
+ apiVersion: v1 kind: Service metadata: name: <%= config.appName %>-<%= app %>
2
+ namespace: <%= config.namespace %> labels: app: <%= config.appName %>-<%= app %>
3
+ spec: type: ClusterIP ports: - port: <%= config.appPorts?.[app] ?? (app ===
4
+ 'api' ? 3000 : 80) %> targetPort: <%= config.appPorts?.[app] ?? (app === 'api' ?
5
+ 3000 : 80) %> protocol: TCP name: http selector: app: <%= config.appName %>-<%=
6
+ app %>
@@ -0,0 +1,133 @@
1
+ name: Deploy to Kubernetes
2
+ on:
3
+ push:
4
+ branches:
5
+ - <%= config.deployBranch || 'production' %>
6
+ workflow_dispatch:
7
+ env:
8
+ REGISTRY: <%= config.containerRegistry %>
9
+ CLUSTER_NAME: <%= config.clusterName %>
10
+ NAMESPACE: <%= config.namespace %>
11
+ API_PUBLIC_URL: <%= config.domain ? `https://api.${config.domain}` : '' %>
12
+ jobs:
13
+ apply-cluster-config:
14
+ name: Apply Kubernetes and Helm configs
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - name: Checkout code
18
+ uses: actions/checkout@v4
19
+ - name: Install doctl
20
+ uses: digitalocean/action-doctl@v2
21
+ with:
22
+ token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
23
+ - name: Set up Helm
24
+ uses: azure/setup-helm@v4
25
+ - name: Save DigitalOcean kubeconfig
26
+ run: doctl kubernetes cluster kubeconfig save ${{ env.CLUSTER_NAME }}
27
+ - name: Ensure namespace exists
28
+ run: |
29
+ kubectl create namespace ${{ env.NAMESPACE }} --dry-run=client -o yaml | kubectl apply -f -
30
+ - name: Apply Kubernetes manifests
31
+ run: |
32
+ <% if (config.setupSSL) { %> kubectl apply -f k8s/cert-manager-issuer.yaml
33
+ <% } %><% (config.apps || []).forEach((app) => { %> kubectl apply -f k8s/<%= app %> -n ${{ env.NAMESPACE }}
34
+ <% }); %><% if (config.setupIngress) { %> kubectl apply -f k8s/ingress.yaml -n ${{ env.NAMESPACE }}
35
+ <% } %><% if ((config.infraServices || []).length > 0) { %> - name: Apply Helm chart(s)
36
+ run: |
37
+ <% config.infraServices.forEach((service) => { %> helm upgrade --install <%= config.appName %>-<%= service.name %> ./helm/services/<%= service.name %> \
38
+ --namespace ${{ env.NAMESPACE }} \
39
+ --create-namespace
40
+ <% }); %><% } %>
41
+
42
+ <% if (config.apps.includes('api')) { %> deploy-api:
43
+ name: Deploy API
44
+ runs-on: ubuntu-latest
45
+ needs: apply-cluster-config
46
+ steps:
47
+ - name: Checkout code
48
+ uses: actions/checkout@v4
49
+ - name: Install doctl
50
+ uses: digitalocean/action-doctl@v2
51
+ with:
52
+ token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
53
+ - name: Log in to Container Registry
54
+ run: doctl registry login
55
+ - name: Build and push Docker image
56
+ run: |
57
+ docker build -t ${{ env.REGISTRY }}/<%= config.appName %>-api:${{ github.sha }} \
58
+ -f apps/api/Dockerfile .
59
+ docker push ${{ env.REGISTRY }}/<%= config.appName %>-api:${{ github.sha }}
60
+ docker tag ${{ env.REGISTRY }}/<%= config.appName %>-api:${{ github.sha }} \
61
+ ${{ env.REGISTRY }}/<%= config.appName %>-api:latest
62
+ docker push ${{ env.REGISTRY }}/<%= config.appName %>-api:latest
63
+ - name: Save DigitalOcean kubeconfig
64
+ run: doctl kubernetes cluster kubeconfig save ${{ env.CLUSTER_NAME }}
65
+ - name: Deploy to Kubernetes
66
+ run: |
67
+ kubectl set image deployment/<%= config.appName %>-api \
68
+ <%= config.appName %>-api=${{ env.REGISTRY }}/<%= config.appName %>-api:${{ github.sha }} \
69
+ -n ${{ env.NAMESPACE }}
70
+ kubectl rollout status deployment/<%= config.appName %>-api -n ${{ env.NAMESPACE }}
71
+
72
+ <% } %><% if (config.apps.includes('admin')) { %> deploy-admin:
73
+ name: Deploy ADMIN
74
+ runs-on: ubuntu-latest
75
+ needs: apply-cluster-config
76
+ steps:
77
+ - name: Checkout code
78
+ uses: actions/checkout@v4
79
+ - name: Install doctl
80
+ uses: digitalocean/action-doctl@v2
81
+ with:
82
+ token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
83
+ - name: Log in to Container Registry
84
+ run: doctl registry login
85
+ - name: Build and push Docker image
86
+ run: |
87
+ docker build -t ${{ env.REGISTRY }}/<%= config.appName %>-admin:${{ github.sha }} \
88
+ --build-arg NEXT_PUBLIC_API_BASE_URL=${{ env.API_PUBLIC_URL }} \
89
+ --build-arg NEXT_PUBLIC_API_URL=${{ env.API_PUBLIC_URL }} \
90
+ -f apps/admin/Dockerfile .
91
+ docker push ${{ env.REGISTRY }}/<%= config.appName %>-admin:${{ github.sha }}
92
+ docker tag ${{ env.REGISTRY }}/<%= config.appName %>-admin:${{ github.sha }} \
93
+ ${{ env.REGISTRY }}/<%= config.appName %>-admin:latest
94
+ docker push ${{ env.REGISTRY }}/<%= config.appName %>-admin:latest
95
+ - name: Save DigitalOcean kubeconfig
96
+ run: doctl kubernetes cluster kubeconfig save ${{ env.CLUSTER_NAME }}
97
+ - name: Deploy to Kubernetes
98
+ run: |
99
+ kubectl set image deployment/<%= config.appName %>-admin \
100
+ <%= config.appName %>-admin=${{ env.REGISTRY }}/<%= config.appName %>-admin:${{ github.sha }} \
101
+ -n ${{ env.NAMESPACE }}
102
+ kubectl rollout status deployment/<%= config.appName %>-admin -n ${{ env.NAMESPACE }}
103
+ <% } %><% (config.apps || []).filter((app) => app !== 'api' && app !== 'admin').forEach((app) => { %>
104
+ deploy-<%= app %>:
105
+ name: Deploy <%= app.toUpperCase() %>
106
+ runs-on: ubuntu-latest
107
+ needs: apply-cluster-config
108
+ steps:
109
+ - name: Checkout code
110
+ uses: actions/checkout@v4
111
+ - name: Install doctl
112
+ uses: digitalocean/action-doctl@v2
113
+ with:
114
+ token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
115
+ - name: Log in to Container Registry
116
+ run: doctl registry login
117
+ - name: Build and push Docker image
118
+ run: |
119
+ docker build -t ${{ env.REGISTRY }}/<%= config.appName %>-<%= app %>:${{ github.sha }} \
120
+ -f apps/<%= app %>/Dockerfile .
121
+ docker push ${{ env.REGISTRY }}/<%= config.appName %>-<%= app %>:${{ github.sha }}
122
+ docker tag ${{ env.REGISTRY }}/<%= config.appName %>-<%= app %>:${{ github.sha }} \
123
+ ${{ env.REGISTRY }}/<%= config.appName %>-<%= app %>:latest
124
+ docker push ${{ env.REGISTRY }}/<%= config.appName %>-<%= app %>:latest
125
+ - name: Save DigitalOcean kubeconfig
126
+ run: doctl kubernetes cluster kubeconfig save ${{ env.CLUSTER_NAME }}
127
+ - name: Deploy to Kubernetes
128
+ run: |
129
+ kubectl set image deployment/<%= config.appName %>-<%= app %> \
130
+ <%= config.appName %>-<%= app %>=${{ env.REGISTRY }}/<%= config.appName %>-<%= app %>:${{ github.sha }} \
131
+ -n ${{ env.NAMESPACE }}
132
+ kubectl rollout status deployment/<%= config.appName %>-<%= app %> -n ${{ env.NAMESPACE }}
133
+ <% }); %>