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
PORTenvironment variable (default8080), 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 asroot. Port8080is the safe default.
Node.js
server.js:
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:
FROM node:20-alpine
WORKDIR /app
COPY server.js .
EXPOSE 8080
USER node
CMD ["node", "server.js"]
Go
main.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:
module hello
go 1.23
Dockerfile (multi-stage โ a tiny distroless image, a few MB):
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:
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:
fastapi==0.115.*
uvicorn[standard]==0.32.*
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.
docker build -t cr.danubedata.ro/acme/hello:1.0.0 .
Optionally run it locally to confirm it works before you push:
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/amd64todocker build(or usedocker buildx), otherwise the pod fails withexec format error.
Step 3: Create a registry access key
- Go to Container Registry โ Access Keys โ New access key.
- Give it a Name (e.g.
laptop) and pick Push + Pull as the scope. - Click Create and copy the token โ it's shown once. Tokens start with
cr_.
Keep it in an environment variable for the next step:
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.
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
- Go to Rapids โ Create Container and choose Docker image as the deployment type.
- Name:
hello - Image:
cr.danubedata.ro/acme/hello:1.0.0 - 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.
- Port:
8080 - 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.)
- Leave Min replicas at
0for 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:
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:
https://hello-acme.danubedata.run
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
- Automate it: CI/CD Build โ Push โ Deploy โ ship a new revision on every
git push. - Production-Ready: Domains, Secrets & Autoscaling โ add your own domain, inject secrets, and tune scaling.
- Container Registry reference โ tag conventions, plans & limits, and the full command set.
Questions? Contact support at support@danubedata.ro