Code management and Git access on the server
How the Ubuntu server gets application source code — and what to use instead once Docker + CI is in place.
All commands below are run on the Ubuntu server over SSH (as your normal login user, not apisvc).
Recommendation (summary)
| Approach | Use when | Verdict |
|---|---|---|
| Your personal SSH key on the server | Never for Git | Avoid — tied to you, often over-privileged |
| Read-only deploy key per repo | Manual git clone / git pull on server | Best for now |
Dedicated deploy user + deploy keys | Same, but cleaner separation | Good if multiple people deploy |
CI builds image → server docker pull | Docker production target | Best long term — no Git on server |
Do not put Git credentials on apisvc. That account runs the API only (nologin, no shell login).
Do not reuse your laptop’s SSH private key on the server for GitHub.
Three accounts — keep roles separate
your-ssh-user SSH login from workstation; runs sudo and git pull
apisvc Runs gunicorn/Docker only; no Git, no login shell
(deploy) Optional: only Git + deploy scripts; no personal identity
Your existing SSH key is for logging into the server, not for GitHub.
Copy-paste: first repo (dwd-api-fastapi)
Run these blocks in order. Replace YOUR_USER only if $USER is empty.
0. Prerequisites (one-time)
# apisvc must exist (see docs/01-directory-layout.md)
id apisvc
# Allow your login user (e.g. gqc) to write under /opt/apis — re-login after usermod
sudo usermod -aG apisvc gqc
sudo mkdir -p /opt/apis
sudo chown root:apisvc /opt/apis
sudo chmod 775 /opt/apis
If you just ran usermod, log out and SSH back in before cloning. Verify with groups (must include apisvc) and touch /opt/apis/.write-test.
1. Generate deploy key
API=dwd-api-fastapi
GITHUB_ORG=gqc
GITHUB_REPO=dwd-api-fastapi
mkdir -p ~/.ssh
chmod 700 ~/.ssh
ssh-keygen -t ed25519 \
-f ~/.ssh/${API}_deploy \
-C "msi-laptop-3-${API}" \
-N ""
chmod 600 ~/.ssh/${API}_deploy
chmod 644 ~/.ssh/${API}_deploy.pub
echo "=== Add this public key to GitHub (next step) ==="
cat ~/.ssh/${API}_deploy.pub
2. Add key on GitHub
- Open:
https://github.com/gqc/dwd-api-fastapi/settings/keys - Add deploy key
- Title:
MSI Laptop 3 — dwd-api-fastapi - Key: paste output from
cat ~/.ssh/${API}_deploy.pub - Allow write access: leave unchecked (read-only)
3. SSH config and GitHub host key
API=dwd-api-fastapi
GITHUB_ORG=gqc
GITHUB_REPO=dwd-api-fastapi
ssh-keyscan -t ed25519 github.com >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
cat >> ~/.ssh/config << EOF
Host github.com-${API}
HostName github.com
User git
IdentityFile ~/.ssh/${API}_deploy
IdentitiesOnly yes
EOF
chmod 600 ~/.ssh/config
4. Test GitHub access
API=dwd-api-fastapi
ssh -T git@github.com-${API}
Expected message (exact wording may vary):
Hi gqc/dwd-api-fastapi! You've successfully authenticated, but GitHub does not provide shell access.
5. Clone into /opt/apis/
Git runs as your login user (so your deploy key is used). Ownership is then set for apisvc.
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}
Verify:
ls -la /opt/apis/dwd-api-fastapi
6. Routine deploy (manual)
API=dwd-api-fastapi
cd /opt/apis/${API}
git pull
# Native:
# sudo -u apisvc /opt/venvs/${API}/bin/pip install -r requirements.txt
# sudo systemctl restart ${API}
# Docker (from /etc/${API}/ or repo compose file):
# docker compose build && docker compose up -d
Copy-paste: additional API repo
Replace placeholders, then run the same blocks as above.
API=report-api # kebab-case api-name
GITHUB_ORG=gqc # GitHub org or user
GITHUB_REPO=report-api # repository name
Generate key, add deploy key on that repo’s GitHub settings, append a new Host block:
API=report-api
ssh-keygen -t ed25519 \
-f ~/.ssh/${API}_deploy \
-C "msi-laptop-3-${API}" \
-N ""
chmod 600 ~/.ssh/${API}_deploy
chmod 644 ~/.ssh/${API}_deploy.pub
cat ~/.ssh/${API}_deploy.pub
cat >> ~/.ssh/config << EOF
Host github.com-${API}
HostName github.com
User git
IdentityFile ~/.ssh/${API}_deploy
IdentitiesOnly yes
EOF
chmod 600 ~/.ssh/config
ssh -T git@github.com-${API}
GITHUB_ORG=gqc
GITHUB_REPO=report-api
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}
GitHub allows one deploy key per repository — each API needs its own key pair and Host github.com-<api-name> entry.
Why not sudo -u apisvc git clone?
Deploy keys live in your ~/.ssh/. The apisvc user has no login shell and should not hold Git credentials.
Pattern used here:
- You run
git clone/git pull(uses your deploy key). apisvcowns the files and runs the application.
Option B — Dedicated deploy user (optional)
Same commands as above, but run after switching to the deploy user (keys live in /home/deploy/.ssh/).
sudo useradd -m -s /bin/bash deploy
sudo usermod -aG docker deploy
sudo usermod -aG apisvc deploy
sudo mkdir -p /opt/apis
sudo chown root:apisvc /opt/apis
sudo chmod 775 /opt/apis
Generate keys and clone as deploy:
sudo su - deploy
mkdir -p ~/.ssh
chmod 700 ~/.ssh
API=dwd-api-fastapi
ssh-keygen -t ed25519 \
-f ~/.ssh/${API}_deploy \
-C "msi-laptop-3-${API}" \
-N ""
chmod 600 ~/.ssh/${API}_deploy
chmod 644 ~/.ssh/${API}_deploy.pub
cat ~/.ssh/${API}_deploy.pub
# Add to GitHub deploy keys, then continue with ssh-keyscan / config / clone as deploy user
Your admin account SSHs in; run sudo -u deploy git -C /opt/apis/dwd-api-fastapi pull for deploys.
Option C — No Git on the server (Docker + CI target)
Once GitHub Actions (or Azure DevOps) builds and pushes images:
Developer → push to GitHub → CI builds image → GHCR/ACR
Server → docker pull (registry credentials only)
No deploy keys on the server. See 05-docker-strategy.md.
Troubleshooting
| Problem | Fix |
|---|---|
Permission denied (publickey) | Confirm deploy key on GitHub; IdentityFile path; IdentitiesOnly yes |
Host key verification failed | Run ssh-keyscan -t ed25519 github.com >> ~/.ssh/known_hosts |
could not lock config file | sudo chown $USER:$USER ~/.ssh/config && chmod 600 ~/.ssh/config |
Permission denied writing /opt/apis | Add user to apisvc: sudo usermod -aG apisvc gqc, re-login; /opt/apis must be 775 not 755 |
git pull cannot write files | sudo chown -R apisvc:apisvc /opt/apis/<api> and sudo chmod -R g+rwX /opt/apis/<api>; confirm you are in group apisvc (groups) |
| Wrong repo authenticated | Each repo needs its own Host github.com-<api-name> alias — do not reuse one Host for multiple repos |
Test which key GitHub sees:
ssh -vT git@github.com-dwd-api-fastapi 2>&1 | grep "Offering public key"
What not to do
| Anti-pattern | Why |
|---|---|
| Copy your laptop’s private key to the server | Same key in two places; often full org access |
| One GitHub PAT on the server with broad repo scope | Larger blast radius than per-repo deploy keys |
git clone as root without fixing ownership | App should be owned by apisvc |
Git credentials on apisvc | Runtime account should not talk to GitHub |
| Deploy keys with write enabled | Server only needs read |
Checklist: first repo
-
id apisvcsucceeds -
sudo usermod -aG apisvc $USERand re-login -
~/.ssh/dwd-api-fastapi_deploycreated (chmod 600) - Public key added to GitHub deploy keys (read-only)
-
Host github.com-dwd-api-fastapiin~/.ssh/config -
ssh -T git@github.com-dwd-api-fastapisucceeds - Cloned to
/opt/apis/dwd-api-fastapi -
sudo chown -R apisvc:apisvc /opt/apis/dwd-api-fastapi
When CI/CD is ready, remove deploy keys from the server and use image: from a registry in compose instead of build:.
Related docs
- Add API checklist: 02-adding-a-new-api.md
- Directory layout: 01-directory-layout.md