Container deployment patterns
NimbusNexus doesn't (yet) ship a managed Kubernetes service. The 80-percent pattern for running a small number of containerized services here: one VM per service, Docker as the container runtime, systemd as the supervisor, a floating IP for the public address. Simple, observable, debuggable.
This guide walks the canonical pattern. For scaled-out services where you really need orchestration (>10 services interacting, complex traffic routing, autoscaling), bring your own Kubernetes (k3s or k0s on a small cluster of VMs, or a managed offering elsewhere); that's outside the scope of this page.
What you'll have at the end
- One VM running your container behind systemd
- A floating IP pointing at it
- Logs flowing to journald (and from there, to wherever you want)
- A clean rolling-deploy story for code updates
1 · Provision a VM
Pick a size that fits your service. For most stateless API/web workloads, gp-1-2 (1 vCPU, 2 GB RAM) is a fine starting point — scale up if you need more.
curl -X POST https://api.nimbusnexus.net/v1/vms \
-H "Authorization: Bearer $NIMBUS_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "api-prod",
"size": "gp-1-2",
"region": "us-east-1",
"image": "ubuntu-24.04",
"user_data": "#!/bin/bash\napt-get update && apt-get install -y docker.io"
}'
user_data runs on first boot — here it installs Docker. By the time the VM transitions to running, Docker is ready.
2 · Allocate a floating IP
curl -X POST https://api.nimbusnexus.net/v1/floating-ips \
-H "Authorization: Bearer $NIMBUS_KEY" \
-H "Content-Type: application/json" \
-d "{ \"region\": \"us-east-1\", \"vm_id\": \"$VM_ID\" }"
The IP attaches to the VM and becomes its public address. Save the IPv4:
export PUBLIC_IP=$(curl -s https://api.nimbusnexus.net/v1/floating-ips/$FLOATING_IP_ID \
-H "Authorization: Bearer $NIMBUS_KEY" | jq -r '.ipv4')
Point your DNS at it (DNS records or your existing registrar).
3 · Build + push your container image
Build your container image somewhere (locally, in CI). Push it to whichever registry you prefer:
- Docker Hub — free for public images.
- GitHub Container Registry (
ghcr.io) — free for public repos, included with GitHub. - NimbusNexus Object Storage — if you'd rather self-host, push tarballs to a private bucket and pull them in the systemd unit (slightly more work to manage tags).
For this guide we'll use ghcr.io/yourorg/api:v1.4.2.
4 · Configure the systemd unit on the VM
SSH to the VM:
ssh ubuntu@$PUBLIC_IP
Create the unit file at /etc/systemd/system/api.service:
[Unit]
Description=API container
After=docker.service network-online.target
Requires=docker.service
[Service]
Restart=always
RestartSec=5
TimeoutStartSec=60
# Pull the image; ignore failures (we'll retry on next start)
ExecStartPre=-/usr/bin/docker pull ghcr.io/yourorg/api:v1.4.2
# Stop any existing container; ignore failure (no container = fine)
ExecStartPre=-/usr/bin/docker stop api
ExecStartPre=-/usr/bin/docker rm api
ExecStart=/usr/bin/docker run \
--name api \
--rm \
--network host \
--env-file /etc/api/env \
ghcr.io/yourorg/api:v1.4.2
ExecStop=/usr/bin/docker stop api
[Install]
WantedBy=multi-user.target
A few notes:
--network host— the container binds to the VM's network directly. Simpler than the default bridge network; works for a single-container setup. Don't use this if you're running multiple containers that need to talk to each other on private ports.--rm— clean up the container after exit. Combined withRestart=always, systemd respawns it on crash.--env-file /etc/api/env— load secrets + config from a file. Keep this file root-readable only (chmod 600).Restart=always— systemd brings the container back on crash. Five-secondRestartSecprevents tight crash loops.
Put your secrets in /etc/api/env:
DATABASE_URL=postgresql://app:••••@db-postgres-prod-01.us-east-1.nimbusnexus.net:6432/app?sslmode=verify-full
NIMBUS_KEY=nn_live_xxxxxxxxxxxxxxxx
JWT_SECRET=...
Enable and start:
sudo systemctl daemon-reload
sudo systemctl enable --now api.service
5 · Verify it's serving
curl http://$PUBLIC_IP/healthz
If you see the response from your service, you're done.
6 · Rolling deploys
To deploy a new version, just update the image tag in the unit file and restart:
sudo sed -i 's|api:v1.4.2|api:v1.4.3|g' /etc/systemd/system/api.service
sudo systemctl daemon-reload
sudo systemctl restart api.service
That's a 1–2 second blip (container stop → image pull → new container start).
For zero-downtime, use the blue/green pattern: two VMs, two containers, swing the floating IP between them at cutover.
7 · Logs
Container stdout/stderr flows into journald automatically:
sudo journalctl -u api.service -f
For longer-term log retention, ship from journald to wherever you store logs:
- systemd-journal-remote — built-in upload to another systemd box.
- Vector — multi-output, including S3-compatible object storage (push to a NimbusNexus bucket).
- Promtail + Loki — Grafana-stack log aggregation.
Why this pattern over Kubernetes
For a single service: Kubernetes is 80 % overhead. The control plane, the scheduler, the manifest YAML — all to run one container. Systemd does the same job in 30 lines.
For two services: still no. Two VMs, two systemd units. Service-to-service traffic over the public floating IPs (or via a private network if you want them off the internet).
For five-plus services: the overhead/value ratio flips. At that point, look at Kubernetes (k3s on a small cluster of VMs is a pragmatic middle ground) or Nomad (lighter than k8s, designed for this size).
What this pattern doesn't do
- No autoscaling. Manual
nimbus vms createscales out; manualnimbus vms deletescales in. Fine for stable workloads; bad for traffic-shaped ones. - No service discovery. Services find each other via known public IPs or DNS — not via in-cluster lookups. For two-or-three-service setups this is fine; beyond that, you want a registry.
- No health-check-driven restarts beyond the container's exit code. systemd restarts on crash; it doesn't restart on "process is running but unhealthy." Add your own watchdog (a sidecar that hits
/healthzandkill -1s the container on failure) if you need this.
Next steps
- Blue/green deploys — zero-downtime version of the deploy story above.
- Virtual machines — every option for the create call.
- Monitor a VM fleet — Prometheus + Grafana setup.
- Static site guide — for pure static content, this pattern is overkill; use object storage instead.