Flask • JSON • Gunicorn • Apache • systemd

So now you have a cloud server. What do you put on it?

A good next project is a simple ____ App. Replace the blank with something you actually care about: Recipe App, Seed App, Movie App, Music App, Book App, Tool App, or anything else you want to organize.

My real example is the Seed App hosted on LaunchShell.com. It started as a practical way to track seeds, trays, grow bags, planting dates, weather, and garden notes.

This is a medium-hardness project. It is doable on a free-tier server or a cheap VPS around $5 per month. I use Hetzner for this kind of practical Linux VPS work, but the same pattern applies on other providers.

Difficulty: Medium
Prerequisite: This guide assumes I already have a cheap cloud server with SSH working. New to that part? Start with the AWS Free Tier Cloud VPS Guide.

The idea

The first app does not need accounts, payments, comments, AI, or a complicated database. It just needs to solve one real problem for me.

Recipe App

Save recipes, ingredients, notes, ratings, photos, and cooking changes.

Seed App

Track seeds, trays, grow bags, planting dates, sprouting, and weather.

Movie App

Track movie files, size, genre, watched status, backup priority, and drive space.

Music App

Track albums, artists, formats, tags, missing artwork, and files to clean up.

The trick: The project becomes easier when the app is based on data I already understand. A boring tutorial app is easy to abandon. My Seed App became useful immediately because LaunchShell.com hosts the real garden workflow I actually use.

What this project teaches

This is where a cloud server turns into a real portfolio project.

App Development

Flask and templates

I create routes, render HTML with Jinja templates, serve static CSS and images, and handle forms.

Simple Storage

JSON-backed data

Instead of starting with a database, I store records in JSON files so the data is easy to inspect, copy, back up, and move.

Server Admin

Real deployment

I run the app with Gunicorn, keep it alive with systemd, put Apache in front, point DNS at it, and add HTTPS.

The simple version

This is the whole project path in one list.

01
Pick the app idea.
Recipe, seed, movie, music, book, tool inventory, or anything personal.
02
Design the data first.
Decide what one item looks like before building screens.
03
Make a tiny Flask app.
One list page, one detail page, one add/edit form.
04
Store data in JSON.
Simple files under data/ before moving to a database later.
05
Run it locally.
Use a Python virtual environment and test in the browser.
06
Deploy it to the server.
Git clone, install requirements, test Gunicorn, then create a systemd service.
07
Put Apache in front.
Public web traffic goes to Apache, then Apache proxies to Flask on localhost.
08
Add DNS, HTTPS, logs, and backups.
Make it public, make it safer, and make it recoverable.

Step-by-step build guide

This is the app-building part before the real deployment setup.

1

I picked a real app idea

I chose one small thing to organize. The best beginner app is personal and boring in a useful way.

  • Recipe App: meals, ingredients, instructions, notes
  • Movie App: titles, file sizes, drives, watched status
  • Music App: albums, artists, formats, cleanup notes
  • Seed App: varieties, planting dates, trays, grow bags
2

I designed one item first

Before writing routes, I wrote one example JSON item. That kept the project from turning into chaos.

{
  "id": "example-001",
  "title": "Example Item",
  "category": "Personal",
  "notes": "What I want to remember",
  "rating": 5,
  "tags": ["favorite", "test"]
}
3

I used a simple project structure

This layout is enough for a first real Flask app.

blank-app/
├── app.py
├── requirements.txt
├── data/
│   └── items.json
├── static/
│   └── style.css
└── templates/
    ├── index.html
    ├── detail.html
    └── form.html
4

I created a Python virtual environment

A virtual environment keeps this app's Python packages separate from the rest of the system.

Action item: New to Linux? Start with the Linux Terminal Intro Guide before running these commands.
cd blank-app
python3 -m venv .venv
source .venv/bin/activate
pip install Flask gunicorn
pip freeze > requirements.txt
5

I wrote the smallest useful Flask app

The first version only needs to load JSON and show it in the browser.

from flask import Flask, render_template
import json
import os

app = Flask(__name__)
DATA_FILE = os.path.join(app.root_path, "data", "items.json")

def load_items():
    with open(DATA_FILE) as f:
        return json.load(f)

@app.route("/")
def index():
    return render_template("index.html", items=load_items())

if __name__ == "__main__":
    app.run(debug=True)
6

I ran it locally first

I tested the app before touching Apache, DNS, HTTPS, or systemd.

source .venv/bin/activate
flask --app app run

Then I opened http://127.0.0.1:5000 in the browser.

7

I added forms only after the list worked

The order matters. First show data. Then add detail pages. Then add create/edit forms.

  • List page: show all items
  • Detail page: show one item
  • Add form: create a new item
  • Edit form: update an item
  • Save function: write JSON back to disk
8

I added basic password protection

For a personal app, I do not want the whole internet editing my recipes, movies, music, or seed collection.

The password should come from an environment variable, not from code committed to GitHub.

APP_PASSWORD=change-this
SECRET_KEY=generate-a-long-random-value

Deploy it on the cheap server

This is the part that makes it feel like a real web app instead of a local Python script.

9

I installed server packages

On the cloud server, I installed Python tools, Apache, Git, and basic security packages.

sudo apt update
sudo apt install -y python3-venv python3-pip apache2 git ufw fail2ban
10

I copied the app to the server

The cleanest version is to push the project to GitHub, then clone it on the server.

mkdir -p ~/apps
cd ~/apps
git clone https://github.com/YOURNAME/blank-app.git
cd blank-app
11

I installed the app dependencies

The server gets its own virtual environment.

python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
12

I tested Gunicorn

Gunicorn runs the Flask app as a real application server. I bound it to localhost so it was not directly exposed to the internet.

.venv/bin/gunicorn -w 2 -b 127.0.0.1:5000 app:app

In another SSH window, I tested:

curl http://127.0.0.1:5000
13

I created an environment file

Secrets do not belong in the Git repository.

sudo nano /etc/blank-app.env
SECRET_KEY=replace-with-a-long-random-string
APP_PASSWORD=replace-with-a-real-password
SESSION_COOKIE_SECURE=1
14

I created a systemd service

systemd keeps the app running, starts it on boot, and gives me logs through journalctl.

sudo nano /etc/systemd/system/blank-app.service
[Unit]
Description=Blank Flask App
After=network.target

[Service]
User=ubuntu
WorkingDirectory=/home/ubuntu/apps/blank-app
EnvironmentFile=/etc/blank-app.env
ExecStart=/home/ubuntu/apps/blank-app/.venv/bin/gunicorn -w 2 -b 127.0.0.1:5000 app:app
Restart=always

[Install]
WantedBy=multi-user.target
15

I enabled the service

This starts the app and makes it come back after reboot.

sudo systemctl daemon-reload
sudo systemctl enable --now blank-app
sudo systemctl status blank-app --no-pager
16

I put Apache in front

Apache handles public web traffic. Flask stays private on 127.0.0.1:5000.

sudo a2enmod proxy proxy_http headers
sudo nano /etc/apache2/sites-available/blank-app.conf
<VirtualHost *:80>
    ServerName app.example.com

    ProxyPreserveHost On
    ProxyPass / http://127.0.0.1:5000/
    ProxyPassReverse / http://127.0.0.1:5000/

    ErrorLog ${APACHE_LOG_DIR}/blank-app-error.log
    CustomLog ${APACHE_LOG_DIR}/blank-app-access.log combined
</VirtualHost>
17

I enabled the Apache site

Then I tested Apache before reloading it.

sudo a2ensite blank-app.conf
sudo apachectl configtest
sudo systemctl reload apache2
18

I pointed DNS at the server

I created an A record for the app subdomain.

Type: A
Name: app
Value: YOUR_SERVER_PUBLIC_IP
Proxy: on, if using Cloudflare for web traffic
19

I added HTTPS

Once the domain reached the server over HTTP, I used Certbot.

sudo apt install -y certbot python3-certbot-apache
sudo certbot --apache -d app.example.com
20

I checked logs and backups

A web app is not finished until I know how to troubleshoot it and back it up.

sudo journalctl -u blank-app -f
sudo tail -f /var/log/apache2/blank-app-error.log
tar -czf ~/blank-app-data-$(date +%F).tar.gz ~/apps/blank-app/data

What to keep generic

This is the reason I would call it a ____ App guide instead of only a Seed App guide.

Part Generic version Examples
Item name item recipe, seed, movie, album, book, tool
Data folder data/items.json or data/items/ data/recipes/, data/movies/, data/albums/
List page Show all items Recipe list, movie library, seed catalog, music collection
Detail page Show one item One recipe, one movie, one album, one seed variety
Form Add/edit item Add recipe notes, update movie drive, edit seed planting info
Deployment Same every time Gunicorn, systemd, Apache, DNS, HTTPS

Safety and scope

This is a good student project, personal tool, or portfolio demo. It is not a full production platform.

Good for

Personal apps

Recipe trackers, small collections, lab dashboards, planting notes, file inventories, and simple internal tools.

Be careful with

User accounts

Do not turn this first version into a public multi-user app with sensitive data. That needs stronger authentication and a real security review.

Upgrade later

Database

JSON is excellent for learning and easy backups. If the app grows, moving to SQLite or PostgreSQL is a natural next step.

What to show in a portfolio

This project is not just "I made a Flask app." It shows the full path from code to server.

Code
Flask routes, templates, forms, and static files
Shows web development basics.
Data
JSON structure and backup strategy
Shows persistence and operational thinking.
Linux
Virtual environment, Gunicorn, systemd
Shows application hosting and service management.
Network
Apache reverse proxy, DNS, HTTPS
Shows how public web traffic reaches a private local app process.
Ops
Logs, restarts, secrets, backups
Shows how I keep the app running and recover when something breaks.

Official docs and friendly references

These are the links I would keep open while building the app.

Final result

At the end, I have a real personal web app running on a cheap cloud server. LaunchShell.com hosts my Seed App, and it has a public domain, HTTPS, a Linux service, a reverse proxy, a backup plan, and data I actually care about. That is a much stronger project than another throwaway tutorial.