Skip to content

Ship your first app (Dockerfile)

This builds an app from a Dockerfile, pushes it to your registry, and serves it over HTTPS. The Dockerfile approach works for any language; this example uses a small Go server to mirror the ko guide. If your app is in Go, ko is the shorter path.

It assumes:

  • You can reach your cluster with kubectl (see the kubectl quickstart).
  • You’ve set up GHCR credentials: a local push login and the ghcr pull secret in the demo namespace (see Set up GHCR credentials).
  • The optional ingress bundle is installed, for HTTPS (see Expose a service with Ingress).
  • You have Docker installed locally. No language toolchain is needed: the build runs inside the image.
Terminal window
mkdir hello && cd hello
Terminal window
cat <<'EOF' > go.mod
module example.com/hello
go 1.23
EOF
Terminal window
cat <<'EOF' > main.go
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "hello from skube - dockerfile")
})
log.Fatal(http.ListenAndServe(":8080", nil))
}
EOF

A multi-stage Dockerfile compiles the binary, then copies it onto a tiny base image:

Terminal window
cat <<'EOF' > Dockerfile
FROM golang:1.23 AS build
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o /hello .
FROM gcr.io/distroless/static
COPY --from=build /hello /hello
ENTRYPOINT ["/hello"]
EOF
Terminal window
docker build -t ghcr.io/<github-username>/hello:v1 .
docker push ghcr.io/<github-username>/hello:v1
Terminal window
cat <<'EOF' | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello
namespace: demo
spec:
replicas: 1
selector:
matchLabels:
app: hello
template:
metadata:
labels:
app: hello
spec:
containers:
- name: hello
image: ghcr.io/<github-username>/hello:v1
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: hello
namespace: demo
spec:
selector:
app: hello
ports:
- port: 80
targetPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: hello
namespace: demo
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: traefik
tls:
- hosts:
- hello.example.com
secretName: hello-tls
rules:
- host: hello.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: hello
port:
number: 80
EOF

The pushed package is private by default, which is why the cluster needs the ghcr pull secret from the credentials guide.

Terminal window
kubectl get certificate hello-tls -n demo -w

Once READY is True:

Terminal window
curl https://hello.example.com

You should get back hello from skube - dockerfile.

Terminal window
kubectl delete namespace demo