Tutorial: Deploy a Flask App with Nix¶
This tutorial walks you through deploying a Flask application on Hop3 using Nix. The expression below pins nixpkgs to a specific commit (nixos-24.11) so the toolchain is reproducible across machines and dates. By the end, you'll have a working web app where every dependency, from Python to Gunicorn, comes from that pinned nixpkgs. Because this example sources Flask and Gunicorn from nixpkgs (via python3.withPackages), those packages are pinned too; templates that instead run pip / npm or fetch upstream binaries at build time are not bit-for-bit reproducible.
Prerequisites¶
- A Hop3 server with Nix installed (the installer does this automatically)
- The Hop3 CLI installed on your local machine
- Nix installed locally for testing (
curl -L https://nixos.org/nix/install | sh)
Verify Nix is working:
Step 1: Create the Project¶
Create app.py:
import os
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return f"Hello from Nix! Running on port {os.environ.get('PORT', '?')}"
@app.route("/health")
def health():
return "ok"
Step 2: Write the Nix Expression¶
Create hop3.nix:
{ pkgs ? import (fetchTarball {
url = "https://github.com/NixOS/nixpkgs/archive/50ab793786d9de88ee30ec4e4c24fb4236fc2674.tar.gz";
sha256 = "1s2gr5rcyqvpr58vxdcb095mdhblij9bfzaximrva2243aal3dgx";
}) {} }:
let
# Define our Python environment with exact packages, drawn from the pinned nixpkgs above
pythonEnv = pkgs.python3.withPackages (ps: with ps; [
flask
gunicorn
]);
# Build the application package
app = pkgs.stdenv.mkDerivation {
pname = "nix-flask-demo";
version = "0.1.0";
src = ./.;
buildInputs = [ pythonEnv ];
# Pure Python app — no compilation needed
dontBuild = true;
installPhase = ''
# Create directories
mkdir -p $out/app $out/bin $out/hop3
# Copy application code
cp app.py $out/app/
# Create a wrapper script
cat > $out/bin/start << 'WRAPPER'
#!/bin/sh
exec ${pythonEnv}/bin/python -m gunicorn app:app "$@"
WRAPPER
chmod +x $out/bin/start
# Runtime configuration for Hop3
cat > $out/hop3/runtime.json << EOF
{
"workers": {
"web": "$out/bin/start --bind \$BIND_ADDRESS:\$PORT --chdir $out/app"
},
"env": {
"FLASK_ENV": "production",
"PYTHONDONTWRITEBYTECODE": "1"
},
"path": [
"$out/bin",
"${pythonEnv}/bin"
]
}
EOF
'';
};
in {
package = app;
}
What this does:
pythonEnvcreates a Python environment with Flask and Gunicorn from nixpkgs- The derivation copies
app.pyto the Nix store and creates a wrapper script runtime.jsontells Hop3 how to start the app: run Gunicorn on$BIND_ADDRESS:$PORT
Step 3: Configure Hop3¶
Create hop3.toml:
That's it. Hop3 reads hop3.nix and handles the rest.
Step 4: Test the Build Locally¶
You should see output like:
Inspect the output:
result=$(nix-build hop3.nix -A package --no-out-link)
cat "$result/hop3/runtime.json"
ls "$result/bin/"
ls "$result/app/"
Optionally, test it runs:
Visit http://localhost:5000: the page shows "Hello from Nix!".
Step 5: Deploy to Hop3¶
Sign in to your server if you have not yet (hop3 login root@your-server.example.com). Then, from the project directory, bind this checkout to it. Signing in recorded it as the context default; until a checkout is bound, commands that reach a server are refused inside it:
Deploy:
Or via git push:
git init && git add -A && git commit -m "Initial commit"
git remote add hop3 ssh://hop3@your-server/home/hop3/repos/nix-flask-demo.git
git push hop3 main
Hop3 will:
- Detect
hop3.nixand select the NixBuilder - Run
nix-buildon the server - Read
runtime.jsonfrom the build output - Start Gunicorn as a managed daemon
- Configure nginx to proxy requests to the app
Step 6: Verify¶
Give the app a hostname, so the proxy routes it, and deploy again:
hop3 env set --app nix-flask-demo HOST_NAME=nix-flask-demo.your-server.example.com
hop3 deploy --app nix-flask-demo
hop3 app status --app nix-flask-demo
curl https://nix-flask-demo.your-server.example.com/
Adding a Database¶
To add PostgreSQL, update hop3.toml:
Hop3 creates the database, then sets DATABASE_URL, PGHOST, PGPORT and the other connection variables in your app's environment. Use them in your Flask app:
What's Different from Native Deployment?¶
| Aspect | Native (buildpack) | Nix |
|---|---|---|
| Dependencies | requirements.txt |
hop3.nix (nixpkgs) |
| Build tool | pip in virtualenv | nix-build |
| Reproducibility | Depends on PyPI state | Reproducible (toolchain pinned via nixpkgs) |
| Build speed | Fast (pip cache) | Slower first build, cached after |
| Configuration | Automatic detection | Explicit hop3.nix |
Next Steps¶
- See Deploying with Nix for patterns in Go, Node.js, Ruby, and static sites
- See Nix Integration Reference for the full
runtime.jsonspecification - Browse working examples in
apps/test-apps-nix/in the Hop3 repository