TechWorld with Nana DevOps Bootcamp, Module 5: Cloud & Infrastructure as Service Basics
Create a server and deploy an application on DigitalOcean.
Technologies: DigitalOcean, Linux (Ubuntu 24.04), Java, Gradle, systemd
What I did:
- Set up and configured a server (Droplet) on DigitalOcean
- Created and configured a new Linux user on the Droplet (security best practice)
- Deployed and ran a Java Gradle application on the Droplet
- Extra: moved the app from a manual
java -jar &to a proper systemd service
IP addresses are redacted in commands and screenshots.
<droplet-ip>= the server's public IP.
ssh root@<droplet-ip>Run on the server. The first value is the IP the server sees me connecting from.
echo $SSH_CLIENTIn the DigitalOcean firewall, allowed inbound traffic only from my IP:
- SSH (port 22)
- App port (7071)
apt update
apt install openjdk-<version>-jre-headless
java -versiongradle buildRun on my laptop, not the server.
scp build/libs/java-react-example.jar root@<droplet-ip>:/rootjava -jar java-react-example.jar &ps aux | grep java
apt install net-tools
netstat -lntpnetstat -lntp shows which port the app is listening on (7071).
Running everything as root is not best practice, so I created a normal user with sudo rights.
adduser nana
usermod -aG sudo nana
su - nana
exitssh nana@<droplet-ip> failed with Permission denied (publickey).
Why: my laptop's public key was only in root's authorized_keys. The new user had none.
Also, my first tries ran ssh from inside the server, which uses the server's keys, not my laptop's. So they would fail either way.
As root:
cd /home/nana
mkdir .ssh
ls -a
vim .ssh/authorized_keys
# i, <paste my public SSH key>, Esc, :wq
exitThen from my laptop:
ssh nana@<droplet-ip>
java -jar java-react-example.jar &Error: Unable to access jarfile means Java can't find the file.
As nana, ~ is /home/nana. The jar was in /root.
Copied the jar to nana's home, gave nana ownership, and stopped the copy running as root:
exit
ssh root@<droplet-ip>
cp /root/java-react-example.jar /home/nana/
chown nana:nana /home/nana/java-react-example.jar
ps aux | grep java
kill 9221
rm /root/java-react-example.jar
su - nana
ls
java -jar java-react-example.jar &chown only changes the copy in /home/nana. The original in /root stays owned by root. So I removed the root copy, so it can't accidentally be run as root again.
The app worked, but I found out java -jar & is not how apps are run on production servers.
# stop the manually started app
ps aux | grep java
kill 9753Plain kill sends SIGTERM, a polite shutdown. The app logged Graceful shutdown complete, and the job exited with code 143 (128 + 15, where 15 = SIGTERM).
Created the service file. Writing to /etc/systemd/system needs root, so sudo is required when logged in as nana:
sudo nano /etc/systemd/system/java-react-example.service
# paste [Unit], [Service] & [Install] info, Ctrl+O + Enter + Ctrl+X
# check the write was successful
cat /etc/systemd/system/java-react-example.serviceThe file is in systemd/java-react-example.service.
Load and start it:
sudo systemctl daemon-reload
sudo systemctl enable --now java-react-exampleWithout sudo, polkit asks for the password once per action, so enable --now asked 3 times (enable, reload, start). sudo asks once.
Manage and observe it:
sudo systemctl status java-react-example # app status
sudo journalctl -u java-react-example -f # app logs
sudo systemctl stop java-react-example
sudo systemctl start java-react-example
sudo systemctl restart java-react-exampleThe logs show GET /countries, which is my browser reaching the app from outside on port 7071:
[Unit]
Description=Java React example app
After=network.target
[Service]
User=nana
WorkingDirectory=/home/nana
ExecStart=/usr/bin/java -jar /home/nana/java-react-example.jar
Restart=on-failure
[Install]
WantedBy=multi-user.target| Line | What it does |
|---|---|
After=network.target |
waits for networking before starting |
User=nana |
runs the app as nana, not root |
ExecStart |
the old java -jar command, with full paths |
Restart=on-failure |
restarts the app if it crashes |
WantedBy=multi-user.target |
with enable, starts the app on boot |
Before, I started the app by hand with java -jar, and if the server rebooted or the app crashed, it stayed down.
Now systemd runs it as a service.
It starts on boot, restarts if it crashes, and runs as nana instead of root.
I control it with systemctl and read its logs with journalctl.
startonly starts a stopped service.restartstops and starts it, for example after uploading a new jar.- After editing the
.servicefile, rundaemon-reloadfirst, or systemd keeps using the old version. - Commands that change the system (
start,stop,enable) needsudo. Commands that only read (status) usually don't. aptinstalls Java system-wide, so installing it once as root makes it available to every user.
.
├── README.md
├── systemd/
│ └── java-react-example.service
└── proofs/ # redacted screenshots









