Deploy an API service
Deploy an API service
Run a lightweight API server on a Quake AI VM: either Node.js (Express) or Python (Flask). The API runs as a systemd service so it starts automatically on boot and restarts on failure.
What you will learn:
- How to open a custom application port in a security group
- How to install a Node.js or Python runtime on Ubuntu
- How to register a long-running process as a systemd service
- How to update and restart the service from your local machine
- How to put Nginx in front of the API as a reverse proxy
Prerequisites#
- A Quake AI account with an active project, see the Quickstart if you have not signed up
- An SSH key pair uploaded to your account
- A working VM you can SSH into, or follow Launch your first server to launch one
Step 1. Create a security group#
Create a security group allowing SSH and your API port:
Step 2. Launch the instance#
Follow Launch your first server to launch an Ubuntu 24.04 instance on the PublicEphemeral network with the s1a.micro flavor, but attach the dev-api security group from Step 1 instead of web-access. The PublicEphemeral network gives the instance a public IP at boot, so no floating IP is needed.
Step 3. Install a runtime and deploy your API#
SSH into the instance and choose one of the two paths below.
Install Node.js and create a minimal API:
# Install Node.js 22 LTS
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejs
# Create the app
mkdir -p ~/api && cd ~/api
npm init -y
npm install expressCreate ~/api/server.js:
cat > ~/api/server.js << 'EOF'
const express = require("express");
const app = express();
const PORT = process.env.PORT || 3000;
app.get("/", (req, res) => {
res.json({ status: "ok", message: "Hello from Quake AI" });
});
app.get("/health", (req, res) => {
res.json({ status: "healthy", uptime: process.uptime() });
});
app.listen(PORT, "0.0.0.0", () => {
console.log(`API listening on port ${PORT}`);
});
EOFCreate a systemd service:
sudo tee /etc/systemd/system/api.service > /dev/null << 'EOF'
[Unit]
Description=Node.js API
After=network.target
[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu/api
ExecStart=/usr/bin/node server.js
Restart=on-failure
RestartSec=5
Environment=PORT=3000
Environment=NODE_ENV=production
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable api
sudo systemctl start apiStep 4. Verify#
Test the API from your local machine:
curl http://YOUR_INSTANCE_IP:3000/
# {"status":"ok","message":"Hello from Quake AI"}
curl http://YOUR_INSTANCE_IP:3000/health
# {"status":"healthy","uptime":42.7}Check the service status on the instance:
sudo systemctl status api
journalctl -u api -fStep 5. Deploy updates#
Push new code to the instance and restart the service:
# From your local machine
scp -r ./api/* ubuntu@YOUR_INSTANCE_IP:~/api/
# On the instance
ssh ubuntu@YOUR_INSTANCE_IP
sudo systemctl restart apiAdding a reverse proxy (optional)#
For production-style deployments, put Nginx in front of your API to serve it on port 80. This example uses plain HTTP; add a TLS certificate and a port 443 listener as a follow-up.
The dev-api security group from Step 1 allows only SSH and port 3000, so external requests to plain http://YOUR_INSTANCE_IP/ time out until you open port 80. Add the rule from your local machine, where the OpenStack CLI is configured:
# Open port 80 to allow external HTTP through Nginx
openstack security group rule create \
--protocol tcp --dst-port 80 --remote-ip 0.0.0.0/0 dev-apiThen install and configure Nginx on the instance:
sudo apt install -y nginx
sudo tee /etc/nginx/sites-available/api > /dev/null << 'EOF'
server {
listen 80;
server_name _;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
EOF
sudo ln -sf /etc/nginx/sites-available/api /etc/nginx/sites-enabled/api
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl reload nginxNext steps#
- How to point a domain at a Quake AI resource: put the API behind your own hostname
- How to issue and auto-renew a TLS certificate with Let's Encrypt: terminate HTTPS on the VM or a self-managed reverse proxy
- How to deploy an application to a Quake AI VM from CI: automate deploys after you outgrow manual
systemctlrestarts - How to store application secrets and inject them at runtime: move API keys out of unit files and into env files
- Deploy a database sandbox: add a database backend to your API
- Deploy a containerized web application: containerize this setup
- Automate your infrastructure with OpenTofu: automate provisioning
See also#
Usage Guidelines
The sample code, software libraries, command line tools, proofs of concept, templates, and other related technology on this page (including any of the foregoing that is provided by Quake AI personnel) is provided to you as Quake AI Content under the Quake AI Customer Agreement, or the relevant written agreement between you and Quake AI (whichever applies). Do not use this Quake AI Content in your production accounts, or on production or other critical data. You are responsible for testing, securing, and optimizing the Quake AI Content (such as sample code) as appropriate for production grade use based on your specific quality control practices and standards. Deploying Quake AI Content may incur Quake AI charges for creating or using Quake AI chargeable resources, such as running Compute instances or storing data in Object Storage. Your use is also subject to the Acceptable Use Policy.
For the full policy, see Usage Guidelines.
Last validated: 04.06.2026
Quick answers
- Why does `openstack image save` write a 0-byte file for my boot-from-volume instance?CLI
- Why does `openstack server create` fail with "Only volume-backed servers are allowed for flavors with zero disk"?CLIAPITerraform
- Why does my project still have a 10 GiB Cinder volume after I deleted my instance?CLIAPI