{"slug":"tutorial-cr-rapids-first-deploy","title":"Push Your First Image to the Container Registry & Deploy on Rapids","description":"This tutorial walks the full push-to-deploy loop end to end: you build a tiny web app","section":"Features","url":"https://docs.danubedata.ro/tutorial-cr-rapids-first-deploy","markdown_url":"https://docs.danubedata.ro/tutorial-cr-rapids-first-deploy.md","breadcrumbs":[{"title":"Features","slug":null},{"title":"Rapids","slug":"serverless-overview"},{"title":"Deploy with CR: First Deploy","slug":"tutorial-cr-rapids-first-deploy"}],"headings":[{"level":1,"title":"Push Your First Image to the Container Registry & Deploy on Rapids","id":"push-your-first-image-to-the-container-registry-deploy-on-rapids"},{"level":2,"title":"What you'll build","id":"what-youll-build"},{"level":2,"title":"Step 1: Write a tiny web app","id":"step-1-write-a-tiny-web-app"},{"level":3,"title":"Node.js","id":"nodejs"},{"level":3,"title":"Go","id":"go"},{"level":3,"title":"Python","id":"python"},{"level":2,"title":"Step 2: Build the image","id":"step-2-build-the-image"},{"level":2,"title":"Step 3: Create a registry access key","id":"step-3-create-a-registry-access-key"},{"level":2,"title":"Step 4: Log in and push","id":"step-4-log-in-and-push"},{"level":2,"title":"Step 5: Deploy on Rapids","id":"step-5-deploy-on-rapids"},{"level":3,"title":"Using the dashboard","id":"using-the-dashboard"},{"level":3,"title":"Or using the CLI","id":"or-using-the-cli"},{"level":2,"title":"Step 6: Test the live URL","id":"step-6-test-the-live-url"},{"level":2,"title":"Recap","id":"recap"},{"level":2,"title":"What's next","id":"whats-next"}],"format":"markdown","word_count":1105,"content":"# Push Your First Image to the Container Registry & Deploy on Rapids\n\nThis tutorial walks the full *push-to-deploy* loop end to end: you build a tiny web app\ninto a container image, push it to your team's private registry at `cr.danubedata.ro`,\nand run it serverless on **Rapids** — with a public HTTPS URL and scale-to-zero.\n\nEverything stays inside DanubeData: pulls from the registry to Rapids travel over the\ncluster LAN (free), and the pull credential is wired up for you automatically.\n\n**Time:** ~15 minutes &nbsp;•&nbsp; **You need:** Docker, a DanubeData account, and a terminal.\n\nThis guide assumes your team slug is `acme` and your account email is `you@example.com` —\nsubstitute your own. Your team slug is the `tenant_name` under **Team Settings → General**.\n\n## What you'll build\n\nA one-file HTTP service that returns JSON on `/` and `200 OK` on `/healthz`, listening on\nport `8080`. We give you the code for **Node.js**, **Go**, and **Python** — pick the one you\nlike. The rest of the series treats it as just \"the image\", so the language stops mattering\nafter this page.\n\n## Step 1: Write a tiny web app\n\nCreate a new empty folder and add the files for your language.\n\n> **Two rules that matter on Rapids:** listen on the `PORT` environment variable (default\n> `8080`), and don't try to bind a port below 1024 — containers run as a non-root user with\n> Linux capabilities dropped, so privileged ports fail even as `root`. Port `8080` is the\n> safe default.\n\n### Node.js\n\n`server.js`:\n\n```js\nconst http = require('node:http');\n\nconst PORT = process.env.PORT || 8080;\n\nconst server = http.createServer((req, res) => {\n  res.setHeader('Content-Type', 'application/json');\n\n  if (req.url === '/healthz') {\n    res.writeHead(200);\n    return res.end(JSON.stringify({ status: 'ok' }));\n  }\n\n  res.writeHead(200);\n  res.end(JSON.stringify({\n    message: 'Hello from Rapids 👋',\n    revision: process.env.K_REVISION || 'local',\n  }));\n});\n\nserver.listen(PORT, () => console.log(`listening on :${PORT}`));\n```\n\n`Dockerfile`:\n\n```dockerfile\nFROM node:20-alpine\nWORKDIR /app\nCOPY server.js .\nEXPOSE 8080\nUSER node\nCMD [\"node\", \"server.js\"]\n```\n\n### Go\n\n`main.go`:\n\n```go\npackage main\n\nimport (\n\t\"encoding/json\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n)\n\nfunc main() {\n\tport := os.Getenv(\"PORT\")\n\tif port == \"\" {\n\t\tport = \"8080\"\n\t}\n\n\thttp.HandleFunc(\"/healthz\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\tjson.NewEncoder(w).Encode(map[string]string{\"status\": \"ok\"})\n\t})\n\n\thttp.HandleFunc(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\tjson.NewEncoder(w).Encode(map[string]string{\n\t\t\t\"message\":  \"Hello from Rapids 👋\",\n\t\t\t\"revision\": os.Getenv(\"K_REVISION\"),\n\t\t})\n\t})\n\n\tlog.Printf(\"listening on :%s\", port)\n\tlog.Fatal(http.ListenAndServe(\":\"+port, nil))\n}\n```\n\n`go.mod`:\n\n```\nmodule hello\n\ngo 1.23\n```\n\n`Dockerfile` (multi-stage → a tiny distroless image, a few MB):\n\n```dockerfile\nFROM golang:1.23-alpine AS build\nWORKDIR /src\nCOPY go.mod main.go ./\nRUN CGO_ENABLED=0 go build -o /app/server .\n\nFROM gcr.io/distroless/static-debian12\nCOPY --from=build /app/server /server\nEXPOSE 8080\nUSER nonroot:nonroot\nENTRYPOINT [\"/server\"]\n```\n\n### Python\n\n`main.py`:\n\n```python\nimport os\nfrom fastapi import FastAPI\n\napp = FastAPI()\n\n@app.get(\"/healthz\")\ndef healthz():\n    return {\"status\": \"ok\"}\n\n@app.get(\"/\")\ndef root():\n    return {\"message\": \"Hello from Rapids 👋\", \"revision\": os.getenv(\"K_REVISION\", \"local\")}\n```\n\n`requirements.txt`:\n\n```\nfastapi==0.115.*\nuvicorn[standard]==0.32.*\n```\n\n`Dockerfile`:\n\n```dockerfile\nFROM python:3.12-slim\nWORKDIR /app\nCOPY requirements.txt .\nRUN pip install --no-cache-dir -r requirements.txt\nCOPY main.py .\nEXPOSE 8080\nRUN useradd -m app\nUSER app\nCMD [\"sh\", \"-c\", \"uvicorn main:app --host 0.0.0.0 --port ${PORT:-8080}\"]\n```\n\n## Step 2: Build the image\n\nTag the image under your team slug from the very first build — the first path segment\n**must** be your slug, or the registry rejects the push.\n\n```bash\ndocker build -t cr.danubedata.ro/acme/hello:1.0.0 .\n```\n\nOptionally run it locally to confirm it works before you push:\n\n```bash\ndocker run --rm -p 8080:8080 cr.danubedata.ro/acme/hello:1.0.0\n# in another terminal:\ncurl localhost:8080\n# {\"message\":\"Hello from Rapids 👋\",\"revision\":\"local\"}\n```\n\n> **Apple Silicon / ARM machines:** the cluster runs `linux/amd64`. If you build on an M-series\n> Mac, add `--platform linux/amd64` to `docker build` (or use `docker buildx`), otherwise the\n> pod fails with `exec format error`.\n\n## Step 3: Create a registry access key\n\n1. Go to **Container Registry → Access Keys → New access key**.\n2. Give it a **Name** (e.g. `laptop`) and pick **Push + Pull** as the scope.\n3. Click **Create** and copy the token — it's shown **once**. Tokens start with `cr_`.\n\nKeep it in an environment variable for the next step:\n\n```bash\nexport DD_REGISTRY_TOKEN=\"cr_xxxxxxxxxxxxxxxxxxxx\"\n```\n\n## Step 4: Log in and push\n\nUse your account email as the Docker username (it's not secret — Docker just needs\n*something* there) and the `cr_...` token as the password.\n\n```bash\necho \"$DD_REGISTRY_TOKEN\" | docker login cr.danubedata.ro -u you@example.com --password-stdin\n\ndocker push cr.danubedata.ro/acme/hello:1.0.0\n```\n\nWhen the push finishes, the tag appears under **Container Registry → Repositories → hello**.\n\n## Step 5: Deploy on Rapids\n\n### Using the dashboard\n\n1. Go to **Rapids → Create Container** and choose **Docker image** as the deployment type.\n2. **Name:** `hello`\n3. **Image:** `cr.danubedata.ro/acme/hello:1.0.0`\n4. **Registry credential:** pick **DanubeData Container Registry** from the dropdown. It's\n   pre-seeded for every team and authenticates against your own registry — you don't create\n   or rotate anything for first-party pulls.\n5. **Port:** `8080`\n6. **Resource profile:** **Free** is enough for this tutorial — it includes the monthly free\n   tier. (See the [pricing page](https://danubedata.ro/pricing) for the paid profiles and\n   pay-per-use rates.)\n7. Leave **Min replicas** at `0` for scale-to-zero and click **Create Container**.\n\nRapids generates a Knative Service, deploys it via GitOps, and provisions a TLS certificate.\n\n### Or using the CLI\n\nIf you have the [DanubeData CLI](https://docs.danubedata.ro/cli-overview) installed and have\nrun `danube login`:\n\n```bash\ndanube rapids create \\\n  --name hello \\\n  --type docker_image \\\n  --image cr.danubedata.ro/acme/hello \\\n  --tag 1.0.0 \\\n  --port 8080 \\\n  --profile free\n```\n\nYou don't pass a registry credential on the CLI — the platform recognises `cr.danubedata.ro`\nimages as first-party and wires the pull secret automatically.\n\n## Step 6: Test the live URL\n\nOnce the status shows **Running**, your container is reachable at:\n\n```\nhttps://hello-acme.danubedata.run\n```\n\n```bash\ncurl https://hello-acme.danubedata.run\n# {\"message\":\"Hello from Rapids 👋\",\"revision\":\"hello-00001\"}\n```\n\nThe `revision` value now comes from Knative instead of `local` — proof you're hitting the\ndeployed container. Leave it idle for a few minutes and it scales to zero; the next request\ncold-starts a fresh instance in milliseconds.\n\n## Recap\n\nYou built an image, pushed it to your private registry, and deployed it serverless — all\nwithin DanubeData, with the registry → Rapids pull staying on the cluster LAN.\n\n## What's next\n\n- **[Automate it: CI/CD Build → Push → Deploy](https://docs.danubedata.ro/tutorial-cr-rapids-cicd)** —\n  ship a new revision on every `git push`.\n- **[Production-Ready: Domains, Secrets & Autoscaling](https://docs.danubedata.ro/tutorial-cr-rapids-production)** —\n  add your own domain, inject secrets, and tune scaling.\n- **[Container Registry reference](https://docs.danubedata.ro/container-registry)** — tag\n  conventions, plans & limits, and the full command set.\n\n---\n\n**Questions?** Contact support at support@danubedata.ro\n","prev":{"title":"Quick Start","slug":"serverless-quickstart","url":"https://docs.danubedata.ro/serverless-quickstart","markdown_url":"https://docs.danubedata.ro/serverless-quickstart.md","json_url":"https://docs.danubedata.ro/serverless-quickstart.json"},"next":{"title":"Deploy with CR: CI/CD","slug":"tutorial-cr-rapids-cicd","url":"https://docs.danubedata.ro/tutorial-cr-rapids-cicd","markdown_url":"https://docs.danubedata.ro/tutorial-cr-rapids-cicd.md","json_url":"https://docs.danubedata.ro/tutorial-cr-rapids-cicd.json"},"index_url":"https://docs.danubedata.ro/index.json"}