| name | deploy-app |
| description | Deploy a containerized application to a Kube-DC project with optional database, service exposure, persistent storage, and gated Shared GPU acceleration. Use for Helm or raw CPU workloads and for Shared GPU requests that must go through the platform preview/create API. |
Prerequisites
- Target project must exist and be Ready
- Project namespace:
{org}-{project}
- For HTTPS exposure: cert-manager
Issuer must exist (or will be created)
- Quota: verify sufficient CPU, memory, and pod capacity before deploying — use the
check-quota skill
- Shared GPU only: verify all three accelerator quota dimensions and use the independent gated creation flow; raw Kubernetes access is not GPU product authorization
Steps
1. Create Namespace Resources (if needed)
If the app needs HTTPS with auto TLS, ensure an Issuer exists:
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: letsencrypt
namespace: {project-namespace}
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: {email}
privateKeySecretRef:
name: letsencrypt-key
solvers:
- http01:
ingress:
ingressClassName: envoy
2. Create the Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: {app-name}
namespace: {project-namespace}
labels:
app: {app-name}
spec:
replicas: {replicas}
selector:
matchLabels:
app: {app-name}
template:
metadata:
labels:
app: {app-name}
spec:
containers:
- name: {app-name}
image: {image}
ports:
- containerPort: {port}
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "512Mi"
2a. Add Shared GPU Acceleration (Optional)
When the user explicitly requests a Shared GPU workload, read
references/shared-gpu.md before generating or
submitting anything. Use the authenticated preview/create endpoints described
there. Do not add native GPU resources to the generic Deployment above and do
not bypass the product creation flag with kubectl apply.
3. Create the Service
For HTTP/HTTPS apps (Gateway Route — recommended):
apiVersion: v1
kind: Service
metadata:
name: {app-name}
namespace: {project-namespace}
annotations:
service.nlb.kube-dc.com/expose-route: "https"
spec:
type: LoadBalancer
ports:
- port: {port}
targetPort: {port}
selector:
app: {app-name}
→ Auto hostname: {service-name}-{project-namespace}.kube-dc.cloud
The hostname is derived from the Service name, not the Deployment or
Helm release name. For Helm charts that use {release}-{chart} as the
default name (e.g. release wp + chart wordpress → Service wp-wordpress),
the hostname becomes wp-wordpress-{project-namespace}.kube-dc.cloud. If
you want a shorter hostname, set fullnameOverride in the chart values.
→ Auto TLS certificate via Let's Encrypt
For TCP/UDP apps (Direct EIP):
apiVersion: kube-dc.com/v1
kind: EIp
metadata:
name: {app-name}-eip
namespace: {project-namespace}
spec:
externalNetworkType: public
---
apiVersion: v1
kind: Service
metadata:
name: {app-name}
namespace: {project-namespace}
annotations:
service.nlb.kube-dc.com/bind-on-eip: "{app-name}-eip"
spec:
type: LoadBalancer
ports:
- port: {port}
targetPort: {port}
selector:
app: {app-name}
4. Add Database (Optional)
If the app needs a database, use the create-database skill to provision one, then
add the secret references to the deployment.
Pick the engine the app actually supports — don't default to PostgreSQL for
everything. Many off-the-shelf charts and apps are MySQL-only:
| Engine | Apps that REQUIRE this engine |
|---|
mariadb (or mysql) | WordPress, Joomla, Drupal (MySQL by default), Magento, Matomo, MediaWiki, phpBB, Moodle, Mautic, Roundcube, Bitnami's *-mariadb charts |
postgresql | Discourse, Mastodon, GitLab, Gitea, Sentry, Keycloak, Harbor, Outline, Plausible, Metabase, NextCloud (recommended), most modern Node/Go/Rust apps |
| Either, app-configurable | Drupal, NextCloud, Redmine, Zabbix, Jira, Confluence — verify the chart's externalDatabase.type / database.type parameter |
Picking the wrong engine produces a tight CrashLoop with confusing log lines —
e.g. WordPress against PostgreSQL boots, sets MARIADB_HOST, then hangs on
/wp-login.php because there's no Postgres driver in WP core.
Bridging KdcDatabase secrets to chart-expected key names. The
KdcDatabase auto-secret stores the password under key password. Many
Helm charts hard-code a different key name:
| Chart | Expected secret key | Workaround |
|---|
| Bitnami WordPress | mariadb-password | Bridge Secret (below) |
| Bitnami Discourse, Bitnami Joomla | db-password | Bridge Secret |
| Most modern charts | configurable via existingSecretPasswordKey | Set the parameter |
When the chart has no existingSecretPasswordKey parameter, create a
small bridge Secret aliasing the password to the chart's expected key name:
PASSWORD=$(kubectl get secret {db-name}-password -n {project-namespace} \
-o jsonpath='{.data.password}' | base64 -d)
kubectl create secret generic {app-name}-db-bridge \
--namespace {project-namespace} \
--from-literal={chart-expected-key}="$PASSWORD" \
--dry-run=client -o yaml | kubectl apply -f -
Then reference existingSecret: {app-name}-db-bridge in the chart values.
PostgreSQL:
PostgreSQL:
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: {db-name}-app
key: password
- name: DB_HOST
value: "{db-name}-rw.{project-namespace}.svc"
- name: DATABASE_URL
value: "postgresql://app:$(DB_PASSWORD)@$(DB_HOST):5432/{database}"
MariaDB:
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: {db-name}-password
key: password
- name: DB_HOST
value: "{db-name}.{project-namespace}.svc"
- name: DATABASE_URL
value: "mysql://app:$(DB_PASSWORD)@$(DB_HOST):3306/{database}"
Helm Deployment Alternative
For Helm-based apps:
helm install {release-name} {chart} \
--namespace {project-namespace} \
--set service.type=LoadBalancer \
--set service.annotations."service\.nlb\.kube-dc\.com/expose-route"=https
Verification
After deploying, run these checks:
kubectl rollout status deployment/{app-name} -n {project-namespace}
kubectl get pods -l app={app-name} -n {project-namespace}
kubectl get endpoints {app-name} -n {project-namespace}
kubectl get svc {app-name} -n {project-namespace} -o jsonpath='{.metadata.annotations.service\.nlb\.kube-dc\.com/route-hostname-status}'
curl -s -o /dev/null -w "%{http_code}" https://\{app-name\}-\{project-namespace\}.kube-dc.cloud
Success: Pods running, endpoints assigned, hostname active, HTTP 200.
Failure:
- Pods not starting:
kubectl describe pod -l app={app-name} -n {project-namespace}
- No endpoints: selector doesn't match pod labels
- No hostname: Issuer may be missing — check
kubectl get issuer -n {project-namespace}
- 503/404: App may not be listening on the expected port
Safety
- For Shared GPU, use the gated backend workflow in references/shared-gpu.md; never raw-apply a handcrafted GPU manifest
- Always set resource requests/limits on containers
- Default to Gateway Route (
expose-route: https) for web apps
- Use Direct EIP only for non-HTTP protocols
- Verify Issuer exists before using
expose-route: https