Skip to main content

Adding a new API

Use this checklist when deploying a new Python API to the server. Complete steps in order. Replace placeholders:

PlaceholderExample
<api-name>customer-api
<port>8001 (see port table in README)
<git-url>git@github.com:org/customer-api.git
<subdomain>customer.example.com
<module>:<app>app.main:app (FastAPI/Uvicorn entrypoint)

Native vs Docker

This checklist is the native path (venv + systemd). If the API runs in Docker, skip Steps 2–3 (venv) and use 05-docker-strategy.md plus the repo’s Dockerfile / compose file. Steps 4 (env file), 6 (Nginx), and 7 (documentation) still apply.


Prerequisites


Step 1 — Create directories

API=<api-name>

sudo mkdir -p /opt/apis/$API
sudo mkdir -p /opt/venvs/$API
sudo mkdir -p /etc/$API
sudo mkdir -p /var/log/$API

sudo chown apisvc:apisvc /opt/apis/$API
sudo chown apisvc:apisvc /opt/venvs/$API
sudo chown root:apisvc /etc/$API
sudo chmod 750 /etc/$API
sudo chown apisvc:apisvc /var/log/$API

Step 2 — Clone application code

Configure a read-only deploy key first — full copy-paste commands in 07-code-management-and-git.md.

Then clone (as your SSH login user; not apisvc):

API=<api-name>
GITHUB_ORG=<org>
GITHUB_REPO=<repo-name>

git clone git@github.com-${API}:${GITHUB_ORG}/${GITHUB_REPO}.git \
/opt/apis/${API}

sudo chown -R apisvc:apisvc /opt/apis/${API}
sudo chmod -R g+rwX /opt/apis/${API}

Example for the first API:

API=dwd-api-fastapi
GITHUB_ORG=gqc
GITHUB_REPO=dwd-api-fastapi

git clone git@github.com-${API}:${GITHUB_ORG}/${GITHUB_REPO}.git \
/opt/apis/${API}

sudo chown -R apisvc:apisvc /opt/apis/${API}
sudo chmod -R g+rwX /opt/apis/${API}

If the repository already exists and you are re-running setup, use git pull instead.

Verify the entrypoint module exists (FastAPI example):

ls /opt/apis/<api-name>/app/main.py   # paths vary by repo

Note the correct Uvicorn target: <module>:<app> (e.g. app.main:app).


Step 3 — Create virtual environment and install dependencies

API=<api-name>

sudo -u apisvc python3 -m venv /opt/venvs/$API
sudo -u apisvc /opt/venvs/$API/bin/pip install --upgrade pip
sudo -u apisvc /opt/venvs/$API/bin/pip install -r /opt/apis/$API/requirements.txt

If the app uses Uvicorn but it is not in requirements.txt:

sudo -u apisvc /opt/venvs/$API/bin/pip install uvicorn

For Azure Storage:

sudo -u apisvc /opt/venvs/$API/bin/pip install azure-storage-blob

Step 4 — Create environment file

Copy the template and fill in values:

API=<api-name>
sudo cp templates/api.env.example /etc/$API/$API.env
sudo chmod 640 /etc/$API/$API.env
sudo chown root:apisvc /etc/$API/$API.env
sudo nano /etc/$API/$API.env

Required variables depend on the application. Common entries:

  • ENV=production
  • Azure credentials (connection string or service principal)
  • Any API keys the app reads from the environment

Test that the app starts with the env file (manual smoke test):

API=<api-name>
PORT=<port>
set -a && source /etc/$API/$API.env && set +a
sudo -u apisvc /opt/venvs/$API/bin/uvicorn <module>:<app> \
--host 127.0.0.1 --port $PORT

Press Ctrl+C after confirming it binds without errors. Then proceed to systemd.


Step 5 — Install systemd service

API=<api-name>
PORT=<port>

sudo cp templates/api.service.template /etc/systemd/system/$API.service
sudo sed -i "s/<api-name>/$API/g" /etc/systemd/system/$API.service
sudo sed -i "s/<port>/$PORT/g" /etc/systemd/system/$API.service
sudo sed -i "s/<module>:<app>/<module>:<app>/g" /etc/systemd/system/$API.service

sudo systemctl daemon-reload
sudo systemctl enable $API
sudo systemctl start $API
sudo systemctl status $API

Verify locally:

curl -s http://127.0.0.1:<port>/health
# or /docs for FastAPI, or / depending on the app

Step 6 — Configure Nginx reverse proxy

When DNS and SSL are ready (or for LAN testing with HTTP only):

API=<api-name>
SUB=<subdomain>

sudo cp templates/nginx-site.template /etc/nginx/sites-available/$API
sudo sed -i "s/<api-name>/$API/g" /etc/nginx/sites-available/$API
sudo sed -i "s/<subdomain>/$SUB/g" /etc/nginx/sites-available/$API
sudo sed -i "s/<port>/<port>/g" /etc/nginx/sites-available/$API

sudo ln -sf /etc/nginx/sites-available/$API /etc/nginx/sites-enabled/$API
sudo nginx -t
sudo systemctl reload nginx

For SSL, add certificate paths to the Nginx block (see 04-nginx-reverse-proxy.md).


Step 7 — Update documentation

On this workstation, edit README.md:

  • Add row to port allocation table
  • Fill in "First API" or add an "Deployed APIs" table
  • Note any repo-specific quirks (custom health path, extra env vars)

Step 8 — Post-deploy verification

# Service running
systemctl is-active <api-name>

# Listening on localhost only
ss -tlnp | grep <port>

# Through Nginx (when configured)
curl -I https://<subdomain>/health

Confirm UFW is not exposing <port> (when UFW is enabled):

sudo ufw status
# Should show 22, 80, 443 only — not 8001, 8002, etc.

Updating an existing API (routine deploy)

Manual deploy pattern until CI/CD is chosen:

API=<api-name>

cd /opt/apis/${API}
git pull
sudo -u apisvc /opt/venvs/${API}/bin/pip install -r requirements.txt
sudo systemctl restart ${API}
journalctl -u ${API} -n 50 --no-pager

If requirements.txt did not change, the pip install step is optional but harmless.


Troubleshooting

SymptomCheck
Service fails immediatelyjournalctl -u <api-name> -n 100 --no-pager
Permission denied on git pullOwnership under /opt/apis/<api-name>; deploy user group
Import errorsVenv path in systemd unit; pip install -r requirements.txt
502 from NginxAPI running? curl http://127.0.0.1:<port>/; check Nginx error log
Env vars not loadedEnvironmentFile= path in unit; file mode 640 and group apisvc

Checklist summary (printable)

[ ] Directories created with correct ownership
[ ] Git repository cloned to /opt/apis/<api-name>/
[ ] Venv at /opt/venvs/<api-name>/ with dependencies installed
[ ] /etc/<api-name>/<api-name>.env created (mode 640)
[ ] Manual uvicorn smoke test passed
[ ] systemd unit installed, enabled, and active
[ ] curl to 127.0.0.1:<port> succeeds
[ ] Nginx site configured and reloaded
[ ] README.md updated on workstation
[ ] Port not exposed in UFW