Push Your First Image to the Container Registry & Deploy on Rapids

This tutorial walks the full push-to-deploy loop end to end: you build a tiny web app into a container image, push it to your team's private registry at cr.danubedata.ro, and run it serverless on Rapids โ€” with a public HTTPS URL and scale-to-zero.

Everything stays inside DanubeData: pulls from the registry to Rapids travel over the cluster LAN (free), and the pull credential is wired up for you automatically.

Time: ~15 minutes ย โ€ขย  You need: Docker, a DanubeData account, and a terminal.

This guide assumes your team slug is acme and your account email is you@example.com โ€” substitute your own. Your team slug is the tenant_name under Team Settings โ†’ General.

What you'll build

A one-file HTTP service that returns JSON on / and 200 OK on /healthz, listening on port 8080. We give you the code for Node.js, Go, and Python โ€” pick the one you like. The rest of the series treats it as just "the image", so the language stops mattering after this page.

Step 1: Write a tiny web app

Create a new empty folder and add the files for your language.

Two rules that matter on Rapids: listen on the PORT environment variable (default 8080), and don't try to bind a port below 1024 โ€” containers run as a non-root user with Linux capabilities dropped, so privileged ports fail even as root. Port 8080 is the safe default.

Node.js

server.js:

JavaScript
const http = require('node:http');

const PORT = process.env.PORT || 8080;

const server = http.createServer((req, res) => {
  res.setHeader('Content-Type', 'application/json');

  if (req.url === '/healthz') {
    res.writeHead(200);
    return res.end(JSON.stringify({ status: 'ok' }));
  }

  res.writeHead(200);
  res.end(JSON.stringify({
    message: 'Hello from Rapids ๐Ÿ‘‹',
    revision: process.env.K_REVISION || 'local',
  }));
});

server.listen(PORT, () => console.log(`listening on :${PORT}`));

Dockerfile:

Dockerfile
FROM node:20-alpine
WORKDIR /app
COPY server.js .
EXPOSE 8080
USER node
CMD ["node", "server.js"]

Go

main.go:

Go
package main

import (
	"encoding/json"
	"log"
	"net/http"
	"os"
)

func main() {
	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
	}

	http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
	})

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(map[string]string{
			"message":  "Hello from Rapids ๐Ÿ‘‹",
			"revision": os.Getenv("K_REVISION"),
		})
	})

	log.Printf("listening on :%s", port)
	log.Fatal(http.ListenAndServe(":"+port, nil))
}

go.mod:

Text
module hello

go 1.23

Dockerfile (multi-stage โ†’ a tiny distroless image, a few MB):

Dockerfile
FROM golang:1.23-alpine AS build
WORKDIR /src
COPY go.mod main.go ./
RUN CGO_ENABLED=0 go build -o /app/server .

FROM gcr.io/distroless/static-debian12
COPY --from=build /app/server /server
EXPOSE 8080
USER nonroot:nonroot
ENTRYPOINT ["/server"]

Python

main.py:

Python
import os
from fastapi import FastAPI

app = FastAPI()

@app.get("/healthz")
def healthz():
    return {"status": "ok"}

@app.get("/")
def root():
    return {"message": "Hello from Rapids ๐Ÿ‘‹", "revision": os.getenv("K_REVISION", "local")}

requirements.txt:

Text
fastapi==0.115.*
uvicorn[standard]==0.32.*

Dockerfile:

Dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY main.py .
EXPOSE 8080
RUN useradd -m app
USER app
CMD ["sh", "-c", "uvicorn main:app --host 0.0.0.0 --port ${PORT:-8080}"]

Step 2: Build the image

Tag the image under your team slug from the very first build โ€” the first path segment must be your slug, or the registry rejects the push.

Bash
docker build -t cr.danubedata.ro/acme/hello:1.0.0 .

Optionally run it locally to confirm it works before you push:

Bash
docker run --rm -p 8080:8080 cr.danubedata.ro/acme/hello:1.0.0
# in another terminal:
curl localhost:8080
# {"message":"Hello from Rapids ๐Ÿ‘‹","revision":"local"}

Apple Silicon / ARM machines: the cluster runs linux/amd64. If you build on an M-series Mac, add --platform linux/amd64 to docker build (or use docker buildx), otherwise the pod fails with exec format error.

Step 3: Create a registry access key

  1. Go to Container Registry โ†’ Access Keys โ†’ New access key.
  2. Give it a Name (e.g. laptop) and pick Push + Pull as the scope.
  3. Click Create and copy the token โ€” it's shown once. Tokens start with cr_.

Keep it in an environment variable for the next step:

Bash
export DD_REGISTRY_TOKEN="cr_xxxxxxxxxxxxxxxxxxxx"

Step 4: Log in and push

Use your account email as the Docker username (it's not secret โ€” Docker just needs something there) and the cr_... token as the password.

Bash
echo "$DD_REGISTRY_TOKEN" | docker login cr.danubedata.ro -u you@example.com --password-stdin

docker push cr.danubedata.ro/acme/hello:1.0.0

When the push finishes, the tag appears under Container Registry โ†’ Repositories โ†’ hello.

Step 5: Deploy on Rapids

Using the dashboard

  1. Go to Rapids โ†’ Create Container and choose Docker image as the deployment type.
  2. Name: hello
  3. Image: cr.danubedata.ro/acme/hello:1.0.0
  4. Registry credential: pick DanubeData Container Registry from the dropdown. It's pre-seeded for every team and authenticates against your own registry โ€” you don't create or rotate anything for first-party pulls.
  5. Port: 8080
  6. Resource profile: Free is enough for this tutorial โ€” it includes the monthly free tier. (See the pricing page for the paid profiles and pay-per-use rates.)
  7. Leave Min replicas at 0 for scale-to-zero and click Create Container.

Rapids generates a Knative Service, deploys it via GitOps, and provisions a TLS certificate.

Or using the CLI

If you have the DanubeData CLI installed and have run danube login:

Bash
danube rapids create \
  --name hello \
  --type docker_image \
  --image cr.danubedata.ro/acme/hello \
  --tag 1.0.0 \
  --port 8080 \
  --profile free

You don't pass a registry credential on the CLI โ€” the platform recognises cr.danubedata.ro images as first-party and wires the pull secret automatically.

Step 6: Test the live URL

Once the status shows Running, your container is reachable at:

Text
https://hello-acme.danubedata.run
Bash
curl https://hello-acme.danubedata.run
# {"message":"Hello from Rapids ๐Ÿ‘‹","revision":"hello-00001"}

The revision value now comes from Knative instead of local โ€” proof you're hitting the deployed container. Leave it idle for a few minutes and it scales to zero; the next request cold-starts a fresh instance in milliseconds.

Recap

You built an image, pushed it to your private registry, and deployed it serverless โ€” all within DanubeData, with the registry โ†’ Rapids pull staying on the cluster LAN.

What's next


Questions? Contact support at support@danubedata.ro