Les solutions cloud sont bonnes pour les projets moyens et grands, mais trop lourdes pour les petits projets personnels. Si vous souhaitez lancer quelque chose de petit (quelques points de terminaison d'API et un petit référentiel), il existe trois options :
J'écris habituellement mes petites applications en utilisant SQLite, c'est une petite base de données de fichier unique pratique qui fonctionne dans n'importe quel langage de programmation et peut être copiée sur une machine locale pour analyser des données par exemple. Je cherchais donc une solution middleware combinant l'approche sans serveur, la facilité de déploiement et la possibilité d'utiliser SQLite et j'ai trouvé Fly.io.
Si vous n'avez pas de compte sur Fly.io, vous devez le créer. Un outil CLI appelé flyctl est également requis pour gérer les projets. Fly.io peut être déployé à la fois localement et depuis CI.
flyctl effectue le déploiement à partir du dossier racine du projet à partir de Dockerfile, ce qui est cool, car le même Dockerfile peut être utilisé dans d'autres systèmes. Pour jouer avec Fly.io, j'ai préparé un projet FastAPI simple qui stocke l'état dans la base de données – un raccourcisseur d'URL générique avec comptage de clics.
Fichier Docker :
FROM python:3.13-alpine WORKDIR /app COPY ./requirements.txt . RUN pip install --no-cache-dir --upgrade -r requirements.txt COPY . /app ENV HOST=0.0.0.0 PORT=8080 EXPOSE ${PORT} CMD uvicorn main:app --host ${HOST} --port ${PORT}
main.py :
import asyncio import random import string from urllib.parse import urlparse import aiosqlite from fastapi import FastAPI, HTTPException, Request from fastapi.responses import RedirectResponse DB_PATH = "/data/app.db" app = FastAPI() async def get_db() -> aiosqlite.Connection: if db := getattr(get_db, "_db", None): if db.is_alive: return db db = await aiosqlite.connect(DB_PATH, loop=asyncio.get_event_loop()) db.row_factory = aiosqlite.Row qs = """ CREATE TABLE IF NOT EXISTS links ( created_at INTEGER DEFAULT (strftime('%s', 'now')), short_code TEXT PRIMARY KEY, full_url TEXT NOT NULL, clicks INTEGER DEFAULT 0 ) """ await db.execute(qs) await db.commit() setattr(get_db, "_db", db) return db def random_code(length=8) -> str: alphabet = string.ascii_letters + string.digits return "".join(random.choice(alphabet) for x in range(length)) def is_valid_url(url: str) -> bool: try: parts = urlparse(url) return all([parts.scheme, parts.netloc]) except ValueError: return False @app.post("/") async def shorten(url: str, req: Request): if not is_valid_url(url): raise HTTPException(status_code=400, detail="Invalid URL") host = req.headers.get("host") if host is None: raise HTTPException(status_code=500, detail="Missing host header") short_code = random_code() db = await get_db() qs = "INSERT INTO links (short_code, full_url) VALUES (?, ?)" await db.execute(qs, (short_code, url)) await db.commit() return f"https://{host}/{short_code}" @app.get("/") async def list_links(): db = await get_db() qs = "SELECT short_code, full_url, clicks FROM links ORDER BY created_at DESC" async with db.execute(qs) as cursor: return await cursor.fetchall() @app.get("/{short_code}") async def redirect(short_code: str): db = await get_db() qs = """ UPDATE links SET clicks = clicks + 1 WHERE short_code = ? RETURNING full_url """ async with db.execute(qs, (short_code,)) as cursor: if row := await cursor.fetchone(): return RedirectResponse(row["full_url"]) raise HTTPException(status_code=404)
requirements.txt :
aiosqlite fastapi uvicorn
Pour déployer notre code, nous devons d'abord créer un projet Fly.io. Cela peut être fait soit dans l'interface Web, soit avec flyctl. Pour créer un projet avec l'outil CLU dans le dossier racine (où se trouve le code), le lancement flyctl doit être exécuté. Cette commande proposera de sélectionner le matériel souhaité et créera le fichier fly.toml :
fly launch --build-only
Vous pouvez modifier le projet à l'avenir en modifiant les paramètres dans ce fichier ou via l'interface utilisateur Web. Le fly.toml de base semble correct, mais SQLite nécessite un stockage, qui peut être créé avec :
fly volumes create sqlite_data -s 1 -r ams
où -s 1 définit la taille du volume sur 1 Go (la valeur par défaut est 3 Go) et -r est la région dans laquelle le volume sera créé (utilisez la même région dans laquelle le projet Fly.io est créé). Vous pourrez toujours modifier la taille de stockage plus tard.
La dernière chose à faire est d'ajouter une section mounts à fly.toml, qui attache le volume à l'application :
FROM python:3.13-alpine WORKDIR /app COPY ./requirements.txt . RUN pip install --no-cache-dir --upgrade -r requirements.txt COPY . /app ENV HOST=0.0.0.0 PORT=8080 EXPOSE ${PORT} CMD uvicorn main:app --host ${HOST} --port ${PORT}
sqlite_data est le nom du stockage, /data est le chemin où le volume sera connecté. C'est essentiellement la même chose que docker run --mount source=sqlite_data,target=/data ou la section Docker Compose correspondante.
SQLite ne peut pas être écrit à partir de plus d'une application, et Fly.io crée par défaut 2 instances pour une application, nous pouvons donc spécifier le nombre de répliques comme une seule au cas où :
import asyncio import random import string from urllib.parse import urlparse import aiosqlite from fastapi import FastAPI, HTTPException, Request from fastapi.responses import RedirectResponse DB_PATH = "/data/app.db" app = FastAPI() async def get_db() -> aiosqlite.Connection: if db := getattr(get_db, "_db", None): if db.is_alive: return db db = await aiosqlite.connect(DB_PATH, loop=asyncio.get_event_loop()) db.row_factory = aiosqlite.Row qs = """ CREATE TABLE IF NOT EXISTS links ( created_at INTEGER DEFAULT (strftime('%s', 'now')), short_code TEXT PRIMARY KEY, full_url TEXT NOT NULL, clicks INTEGER DEFAULT 0 ) """ await db.execute(qs) await db.commit() setattr(get_db, "_db", db) return db def random_code(length=8) -> str: alphabet = string.ascii_letters + string.digits return "".join(random.choice(alphabet) for x in range(length)) def is_valid_url(url: str) -> bool: try: parts = urlparse(url) return all([parts.scheme, parts.netloc]) except ValueError: return False @app.post("/") async def shorten(url: str, req: Request): if not is_valid_url(url): raise HTTPException(status_code=400, detail="Invalid URL") host = req.headers.get("host") if host is None: raise HTTPException(status_code=500, detail="Missing host header") short_code = random_code() db = await get_db() qs = "INSERT INTO links (short_code, full_url) VALUES (?, ?)" await db.execute(qs, (short_code, url)) await db.commit() return f"https://{host}/{short_code}" @app.get("/") async def list_links(): db = await get_db() qs = "SELECT short_code, full_url, clicks FROM links ORDER BY created_at DESC" async with db.execute(qs) as cursor: return await cursor.fetchall() @app.get("/{short_code}") async def redirect(short_code: str): db = await get_db() qs = """ UPDATE links SET clicks = clicks + 1 WHERE short_code = ? RETURNING full_url """ async with db.execute(qs, (short_code,)) as cursor: if row := await cursor.fetchone(): return RedirectResponse(row["full_url"]) raise HTTPException(status_code=404)
Toutes les configurations sont terminées maintenant et nous pouvons déployer notre application avec la commande :
aiosqlite fastapi uvicorn
L'application devrait démarrer avec succès et le nom DNS public sera imprimé sur la console. Nous pouvons maintenant le vérifier en publiant une URL sur le raccourcisseur :
fly launch --build-only
Ensuite, nous pouvons visiter ce lien, il devrait rediriger vers https://example.com. Enfin, nous pouvons vérifier que les clics sont mis à jour :
fly volumes create sqlite_data -s 1 -r ams
Pour vérifier l'état de la base de données enregistré entre les déploiements, nous pouvons effectuer un nouveau déploiement avec fly déployer et vérifier que la liste des liens est restée la même que ci-dessus (1 lien, 1 clic).
Si vous utilisez une solution externe pour les migrations, plutôt que de les exécuter à partir du code au démarrage de l'application, le seul moyen d'exécuter la migration est de la placer dans Dockerfile dans le cadre de la commande RUN.
Nous pouvons nous connecter à la machine avec la console fly ssh, puis dans le dossier /data interagir avec le fichier de base de données. Nous pouvons également copier le fichier de base de données sur la machine locale avec :
[mounts] source = "sqlite_data" destination = "/data"
Fly.io est un service simple et pratique pour déployer des applications. Déployez les travaux à partir de Docker Containers, les services supplémentaires incluent PSQL, Redis, le stockage de type S3 (contrairement à Vercel). C'est bon marché, le service le moins cher coûte 3 dollars (1 CPU partagé / 256 Mo) – peut-être même moins si vous avez peu de trafic – le conteneur s'éteint après quelques minutes d'inactivité et se rallume automatiquement lorsque du trafic apparaît.
Par contre, il n'y a pas de solution intégrée pour les tâches planifiées – à la place, la solution officielle consiste à configurer un serveur séparé avec crontab et à exécuter des tâches à partir de celui-ci – c'est un peu effrayant.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!