All writing Notes & observations

K8s Container Cloud Deployment: From Dockerfile Image Build to Release

A practical walkthrough for deploying services on an internal Kubernetes container cloud platform, with extra focus on Dockerfile design, image builds, registry push, workload configuration, rollout validation, and troubleshooting.

K8s Container Cloud Deployment: From Dockerfile Image Build to Release

When deploying services to an internal Kubernetes container cloud platform, the console form is only the final step. The real deployment path is: the code builds reliably, the Dockerfile produces a correct image, the image runs locally, the image is pushed to a registry, and Kubernetes can pull and run it with healthy probes.

K8s container cloud deployment workflow

This article follows the order of a real deployment and uses the China Telecom Cloud CeakE Container Service CKE console for the UI walkthrough. Other consoles may use different labels, but the underlying Kubernetes concepts—workloads, Services, Ingress, probes, and rolling updates—map closely.

flowchart LR A[Source code] --> B[Write Dockerfile] B --> C[Build image locally] C --> D[Run and verify locally] D --> E[Push to registry] E --> F[Create workload on platform] F --> G[Configure Service and Ingress] G --> H[Validate release and prepare rollback]

1. What to confirm before deployment

Write down the deployment inputs before touching the platform page.

Item What to confirm Example
Startup command How the service starts and which port it listens on java -jar app.jar, ./server, npm start, port 8080
Dockerfile Whether the project has a repeatable image build Dockerfile, .dockerignore
Registry Registry address, project path, credentials registry.example.com/team/order-api
Namespace Target project or Kubernetes namespace team-dev, team-test, team-prod
Configuration Runtime config and secrets APP_ENV, database URL, tokens
Network exposure Internal-only or external access ClusterIP, Ingress, Route, gateway
Resources CPU, memory, storage, replicas requests: 200m/256Mi, limits: 1/1Gi
Health checks Endpoint used by the platform to judge health /healthz, /actuator/health

Most platform UI fields are wrappers around Kubernetes objects:

Platform term Kubernetes object Purpose
Application / workload Deployment Manages replicas, rolling updates, and rollback
Instance / container group Pod Smallest unit that runs containers
Service Service Stable in-cluster endpoint for Pods
Route / domain / gateway Ingress or platform entry object External HTTP/HTTPS entry
Configuration ConfigMap Non-sensitive configuration
Secret Secret Passwords, tokens, certificates
Registry credential imagePullSecret Allows nodes to pull from private registries

The platform hides some YAML, but Kubernetes is still the execution layer underneath.

2. The key part: building an image from Dockerfile

Kubernetes does not run source code directly. A Deployment points to an image, the node pulls that image from a registry, and the container runtime starts it as a container.

Many deployment failures actually start at the image layer:

  • the build artifact was not copied into the final image;
  • the container startup command is wrong;
  • the service listens on 127.0.0.1 instead of 0.0.0.0;
  • required runtime config was neither baked into the image nor injected by the platform;
  • the image architecture does not match cluster nodes;
  • the private registry address, tag, or credential is wrong.

Dockerfile instructions you should recognize

Instruction Purpose Note
FROM Selects the base image Prefer explicit versions over latest
WORKDIR Sets the working directory Affects later COPY, RUN, and CMD
COPY Copies files from build context Prefer COPY unless ADD is truly needed
RUN Runs commands during image build Used for dependency install and compilation
ARG Build-time variable Not normally kept as runtime env
ENV Runtime environment default Do not store passwords here
EXPOSE Documents the container port It does not publish the port by itself
USER Selects runtime user Avoid running production containers as root
ENTRYPOINT Fixed executable entry Good for the main process
CMD Default command or arguments Can be overridden by docker run

The final . in this command is the build context:

1
docker build -t registry.example.com/team/order-api:1.0.0 .

Everything inside that context can be sent to the Docker builder, so add a .dockerignore:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
.git
.github
.idea
.vscode
node_modules
target
dist
build
*.log
.env
.env.*
README.md

This improves build speed, reduces image risk, and helps Docker layer caching behave predictably.

A practical image build flow

flowchart TD A[Verify native build] --> B[Write .dockerignore] B --> C[Write Dockerfile] C --> D[docker build] D --> E[docker run locally] E --> F[Tag image] F --> G[docker login] G --> H[docker push] H --> I[Deploy image on platform]

First confirm the project builds without Docker:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Java example
./mvnw clean package -DskipTests

# Go example
go test ./...
go build -o server ./cmd/server

# Node.js example
npm ci
npm run build

Dockerfile should make a stable build repeatable. It should not hide a broken local build.

Go service example

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# syntax=docker/dockerfile:1

FROM golang:1.23-alpine AS builder
WORKDIR /src

RUN apk add --no-cache git ca-certificates

COPY go.mod go.sum ./
RUN go mod download

COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /out/server ./cmd/server

FROM alpine:3.20
WORKDIR /app

RUN addgroup -S app && adduser -S app -G app
COPY --from=builder /out/server /app/server
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

USER app
EXPOSE 8080
ENTRYPOINT ["/app/server"]

Useful details:

Detail Reason
Copy go.mod and go.sum before source files Dependency download can use cache
Set CGO_ENABLED=0 GOOS=linux GOARCH=amd64 Produces a Linux amd64 binary for common cluster nodes
Use a separate runtime stage Keeps source and build cache out of the final image
Run as app user Avoids root runtime by default

Build and run locally:

1
2
3
4
5
6
7
8
docker build -t registry.example.com/team/order-api:1.0.0 .

docker run --rm --name order-api \
  -p 8080:8080 \
  -e APP_ENV=local \
  registry.example.com/team/order-api:1.0.0

curl http://localhost:8080/healthz

If this local check fails, fix the image before touching Kubernetes. Check the actual port, bind address, startup command, health endpoint, and required environment variables.

Java Spring Boot example

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# syntax=docker/dockerfile:1

FROM eclipse-temurin:21-jdk AS builder
WORKDIR /src

COPY mvnw pom.xml ./
COPY .mvn .mvn
RUN ./mvnw -q -DskipTests dependency:go-offline

COPY src src
RUN ./mvnw -q -DskipTests package

FROM eclipse-temurin:21-jre
WORKDIR /app

RUN useradd -r -u 10001 appuser
COPY --from=builder /src/target/*.jar /app/app.jar

USER appuser
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/app.jar"]

If the service needs JVM options, keep them configurable:

1
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /app/app.jar"]

Then inject runtime values from the platform:

1
2
JAVA_OPTS=-Xms512m -Xmx512m -XX:+UseG1GC
SPRING_PROFILES_ACTIVE=prod

Remember that JAVA_OPTS only works if the entrypoint actually reads it.

Node.js service example

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# syntax=docker/dockerfile:1

FROM node:22-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

FROM node:22-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
RUN npm prune --omit=dev

FROM node:22-alpine
WORKDIR /app
ENV NODE_ENV=production

RUN addgroup -S app && adduser -S app -G app
COPY --from=builder /app/package*.json ./
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist

USER app
EXPOSE 3000
CMD ["node", "dist/server.js"]

For a static frontend, the runtime image can be Nginx instead of Node:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM nginx:1.27-alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80

Image tag strategy

Avoid using only latest in production. It is hard to trace and hard to rollback.

Better options:

1
2
3
registry.example.com/<team>/<service>:<version>
registry.example.com/<team>/<service>:<git-short-sha>
registry.example.com/<team>/<service>:<environment>-<build-number>

Example:

1
2
3
docker build -t registry.example.com/pay/order-api:1.4.2 .
docker build -t registry.example.com/pay/order-api:git-a18c3f2 .
docker build -t registry.example.com/pay/order-api:test-20260903-001 .

At minimum, keep the Git SHA:

1
2
3
4
5
GIT_SHA=$(git rev-parse --short HEAD)
IMAGE=registry.example.com/pay/order-api:${GIT_SHA}

docker build -t ${IMAGE} .
docker push ${IMAGE}

If the platform supports image digests, production can pin an immutable reference:

1
registry.example.com/pay/order-api@sha256:xxxxxxxxxxxxxxxx

Tags are readable; digests are immutable.

Match image architecture with cluster nodes

If the image is built on ARM but the cluster runs amd64 nodes, the Pod may fail with exec format error.

Build amd64 explicitly:

1
2
3
4
docker buildx build \
  --platform linux/amd64 \
  -t registry.example.com/team/order-api:git-a18c3f2 \
  --push .

Push to registry

Check the local image:

1
docker images | grep order-api

Log in:

1
docker login registry.example.com

Retag if needed:

1
docker tag order-api:local registry.example.com/pay/order-api:git-a18c3f2

Push:

1
docker push registry.example.com/pay/order-api:git-a18c3f2

The cluster will not use images stored only in your local Docker daemon. A real platform pulls from the registry.

3. Create the workload on the platform

Once the image exists in the registry, create the workload. The screenshots below show the complete console flow.

Check the cluster and available capacity

Open Container → Cluster List, locate the target cluster, and confirm that it is running. Check the cluster type, version, node count, and tenant before entering the console so the service is not deployed to the wrong environment.

CeakE CKE cluster list

The cluster overview summarizes Deployment, StatefulSet, DaemonSet, Job, CronJob, and Pod counts together with node health and CPU, memory, and disk usage.

CeakE CKE cluster resource overview

Do not rely on the green node status alone. Check resource headroom and recent warning events as well. If requests exceed the remaining allocatable resources, a new Pod can stay Pending even when every node is healthy.

Choose the correct workload type

The Workloads menu contains more than Deployment. Choose the controller based on how the process should run:

Console entry Kubernetes object Best fit
Stateless workload Deployment Replaceable and horizontally scalable web or API services
DaemonSet DaemonSet One instance per node, such as logging or monitoring agents
Stateful workload StatefulSet Stable identity, ordered rollout, or persistent storage
Job Job One-time work that exits after completion
CronJob CronJob Work executed periodically from a Cron schedule
Pod Pod Inspecting and troubleshooting runtime instances; rarely a direct long-lived deployment choice

Deployment list

The running/desired Pod count, replica count, and status provide a quick rollout check. A row showing 0/1 and Updating, for example, has not reached the desired state.

DaemonSet list

StatefulSet list

Job list

CronJob list

Pod list

The Pod page is especially useful during incidents because it shows status, ready containers, restart count, Pod IP, and node. The CrashLoopBackOff row and very high restart count in the screenshot are direct failure signals; inspect container logs, previous logs, and Pod events before changing Service or Route settings.

Create a Deployment with the four-step wizard

From Stateless Workload, select Create Stateless Workload. The wizard is divided into Basic Information, Container Configuration, Service Configuration, and Advanced Configuration.

Step 1: basic information

Deployment basic information

Enter the name, project (the Kubernetes namespace), replica count, and description. One replica is enough for an initial test; production commonly starts with at least two replicas and a rolling strategy that preserves availability.

Step 2: container configuration

Deployment image and resource configuration

The image can be selected from the platform registry or entered as a full image reference. Verify the tag, pull policy, and vCPU/memory requests and limits:

  • IfNotPresent works well with immutable tags. The screenshot shows Always as the default; that is useful when tags can be overwritten, but production releases should still use unique tags.
  • Avoid leaving all CPU and memory controls unlimited. Set realistic requests, then define limits from load-test and monitoring data.
  • The container name identifies a container inside the Pod; it does not have to match the Deployment, and a Pod can contain multiple containers.

Deployment environment, probes, and storage configuration

Further down, the form exposes environment variables, command and arguments, working directory, storage, and all three Kubernetes probes:

  • Readiness decides whether the Pod is included in Service endpoints and should normally be enabled for a network service.
  • Liveness decides whether Kubernetes should restart the container; avoid coupling it to unstable downstream dependencies.
  • Startup protects slow-starting applications from being restarted by liveness checks before initialization completes.

Step 3: service configuration

Service configuration inside the Deployment wizard

Bind an existing Service or create one directly from the wizard. This step can be skipped when the workload does not need an endpoint yet; a Service can be added later from the Network menu.

Create a Service inside the Deployment wizard

Select the access type, protocol, Service port, and container port. Prefer ClusterIP for ordinary internal services. NodePort opens a port on the nodes, while LoadBalancer depends on the cloud integration. Most importantly, the Service selector must match the workload labels or no endpoint will be created.

Step 4: advanced configuration

Deployment networking and rolling-update configuration

The first part contains DNS and additional host/network settings plus the rollout strategy. ClusterFirst is the usual DNS policy. With RollingUpdate, choose maxSurge and maxUnavailable together with the replica count; a single-replica service that allows one unavailable Pod will have downtime during a rollout.

Deployment scheduling, labels, and image-secret configuration

The remaining controls cover node affinity, workload affinity/anti-affinity, tolerations, labels, annotations, monitoring, and private image credentials. Change scheduling rules only for a real placement requirement, and select an image secret when the cluster must authenticate to a private registry.

The four-step form ultimately produces a Kubernetes Deployment. Use the following table as a field checklist:

Field Recommended value Note
Application name order-api Keep it close to the service name
Namespace team-test / team-prod Separate environments and teams
Image registry.example.com/pay/order-api:git-a18c3f2 Use the pushed image
Replicas 1 for test, 2+ for production Production needs multiple replicas for rolling updates
Container port 8080 Must match the real listening port
Environment variables Platform form or ConfigMap Do not bake environment differences into the image
Secrets Secret Do not put passwords in Dockerfile
CPU/memory Start small, then tune requests for scheduling, limits for upper bounds
Health check /healthz or service health endpoint Readiness controls traffic routing
Pull credential Registry secret Required for private registries

A typical Deployment:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-api
  namespace: team-test
  labels:
    app: order-api
spec:
  replicas: 2
  revisionHistoryLimit: 5
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 0
      maxSurge: 1
  selector:
    matchLabels:
      app: order-api
  template:
    metadata:
      labels:
        app: order-api
    spec:
      imagePullSecrets:
        - name: registry-secret
      containers:
        - name: order-api
          image: registry.example.com/pay/order-api:git-a18c3f2
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 8080
          env:
            - name: APP_ENV
              value: "test"
          envFrom:
            - configMapRef:
                name: order-api-config
            - secretRef:
                name: order-api-secret
          resources:
            requests:
              cpu: "200m"
              memory: "256Mi"
            limits:
              cpu: "1"
              memory: "1Gi"
          readinessProbe:
            httpGet:
              path: /healthz
              port: http
            initialDelaySeconds: 10
            periodSeconds: 10
            timeoutSeconds: 2
            failureThreshold: 3
          livenessProbe:
            httpGet:
              path: /healthz
              port: http
            initialDelaySeconds: 30
            periodSeconds: 20
            timeoutSeconds: 2
            failureThreshold: 3

The three most important fields are image, containerPort, and readinessProbe. If readiness does not pass, the Pod can be running but still receive no traffic.

4. Configure service entry

Deployment makes Pods run. Service and Ingress make them reachable.

flowchart LR U[User or upstream service] --> I[Ingress or gateway] I --> S[Service] S --> P1[Pod 1] S --> P2[Pod 2] S --> P3[Pod 3]

Create a Service

The Network → Service page shows the access type, Cluster IP or external IP, and each Service-port-to-container-port mapping. ClusterIP, NodePort, and LoadBalancer expose the application at different scopes, so a port number alone does not tell you whether the service is publicly reachable.

Service list and port mappings

Create Service form

Choose the project and name, then set the access type and ports:

Form field Kubernetes field Meaning
Service port spec.ports[].port Port used by clients of the Service
Container port spec.ports[].targetPort Port that receives the traffic in the Pod
Access type spec.type ClusterIP, NodePort, or LoadBalancer
Workload/label binding spec.selector Must match labels on the target Pods
Session Hold sessionAffinity Enable only when the application requires sticky sessions

Internal access usually uses a Service:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
apiVersion: v1
kind: Service
metadata:
  name: order-api
  namespace: team-test
spec:
  type: ClusterIP
  selector:
    app: order-api
  ports:
    - name: http
      port: 80
      targetPort: http

Other in-cluster services can call:

1
http://order-api.team-test.svc.cluster.local

External HTTP/HTTPS access usually uses Ingress, Route, or a company gateway:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: order-api
  namespace: team-test
spec:
  ingressClassName: nginx
  rules:
    - host: order-api-test.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: order-api
                port:
                  number: 80

Create a Route (Ingress)

This platform exposes Ingress under Network → Route. The list shows the route name, bound Services, project, and type; a single route can contain multiple backend rules.

Route list

Create Route form

Enter the route name, project, Ingress type, protocol, host, path matching mode, target Service, and target port. The screenshot uses type nginx and match mode Prefix, which correspond to the Ingress controller and pathType: Prefix. HTTPS additionally requires the TLS certificate or Secret expected by the platform.

A console may call this object Ingress, domain, route, layer-7 load balancer, or application entry. Whatever the label, troubleshoot the same chain: Route/Ingress → Service → Endpoint → Pod.

5. Validate release and prepare rollback

If YAML deployment is supported:

1
2
3
kubectl apply -f k8s/
kubectl rollout status deployment/order-api -n team-test
kubectl get pods -n team-test -l app=order-api -o wide

Useful checks:

1
2
3
4
5
6
kubectl get pods -n team-test -l app=order-api
kubectl describe deployment order-api -n team-test
kubectl describe pod <pod-name> -n team-test
kubectl logs -f deployment/order-api -n team-test
kubectl port-forward svc/order-api 8080:80 -n team-test
curl http://localhost:8080/healthz

Rollback:

1
2
3
kubectl rollout history deployment/order-api -n team-test
kubectl rollout undo deployment/order-api -n team-test
kubectl rollout status deployment/order-api -n team-test

Platform consoles usually expose the same behavior as version history, rollback, or redeploy actions.

6. Common troubleshooting table

Symptom Likely cause Where to look
ImagePullBackOff Wrong image, missing tag, registry permission issue Registry, imagePullSecret, Pod events
ErrImagePull Node failed to pull image kubectl describe pod
CrashLoopBackOff Container starts and exits repeatedly kubectl logs --previous, startup command, env vars
Running but 0/1 Ready Readiness probe fails Health endpoint, port, timeout
External 404 Ingress host or path mismatch Ingress and gateway rules
External 502/503 Service has no ready endpoint Pod readiness, Service selector
Database connection failure Secret, network policy, allowlist, DNS Env vars, credentials, network
OOMKilled Memory exceeded limit Raise limit, inspect memory usage
exec format error Image architecture mismatch Rebuild with buildx --platform linux/amd64
New image not picked up Same tag reused and pull policy did not force pull Use a new tag or adjust imagePullPolicy

Recommended troubleshooting order:

flowchart TD A[Was Pod created] -->|No| B[Check Deployment events] A -->|Yes| C[Was image pulled] C -->|No| D[Check image address and registry credential] C -->|Yes| E[Did container start] E -->|No| F[Check startup command and logs] E -->|Yes| G[Is Pod Ready] G -->|No| H[Check readiness probe] G -->|Yes| I[Does Service have endpoints] I -->|No| J[Check selector and labels] I -->|Yes| K[Check Ingress or gateway]

7. Pre-release checklist

Check Pass criteria
Dockerfile is repeatable docker build works on another machine or CI
Image runs locally docker run passes the health check
Tag is traceable Tag includes version, build number, or Git SHA
Image is pushed The platform can pull it from the registry
Config is not baked into image Environment differences use ConfigMap / Secret
Secrets are not in Dockerfile No password in ENV, source, or image layers
Resources are set Reasonable requests and limits exist
Probes are set Readiness and liveness endpoints work
Service selector is correct Service has Pod endpoints
Entry route is verified Domain, path, certificate, gateway are correct
Rollback is clear Previous image tag or Deployment revision is known
Logs and metrics are available Operators can inspect runtime behavior after release

Summary

The deployment path can be summarized as:

Use Dockerfile to turn the service into a runnable, traceable, pushable image; then let Kubernetes manage replicas with Deployment, provide stable access with Service, and route external traffic through Ingress or a platform gateway.

Four habits make this process much more reliable:

  1. run the image locally before pushing it;
  2. use a unique tag for every release instead of relying on latest;
  3. inject configuration and secrets from the platform instead of baking them into the image;
  4. prepare health checks, logs, and rollback before release.

Once this chain is stable, deploying through a container cloud platform becomes a standardized delivery workflow instead of trial and error in a web form.