Core Concepts#

Before diving into the deployment steps, this page explains the foundational concepts behind every tool and decision made. Reading this first will make everything else click.

Environment Variables#

Environment variables are key-value pairs that your operating system makes available to any running process. They allow the same codebase to behave differently depending on where it runs — local machine, staging, production — without changing any code.

Your Python app reads them like this:

import os
AUTH0_DOMAIN = os.environ["AUTH0_DOMAIN"]

The .env file is a plain text file that lives in your project root and holds your variables locally:

AUTH0_DOMAIN=auth.geoassistant.org
AUTH0_CLIENT_SECRET=your_secret_here

The library python-dotenv reads this file at startup and loads the values into the environment automatically. Your code never changes — it always uses os.environ — it just doesn’t care where the values came from.

Warning

Never commit .env to Git. Add it to .gitignore. Instead, commit a .env.example with the same keys but empty values, so other developers know what to set.

On production servers (Railway), variables are set directly in the dashboard UI — no .env file needed.

SSH Keys#

SSH (Secure Shell) is a protocol for securely authenticating to remote machines and services. An SSH key pair consists of two mathematically linked strings:

  • Private key — stays on your machine, never shared.

  • Public key — given to the service you want to access (e.g., GitHub).

When you authenticate, the service sends a random challenge string. Your machine uses the private key to sign it (encrypt it), and sends the signature back. The service verifies the signature using your public key. If it matches, you’re in. The private key never leaves your machine.

# Generate a key pair
ssh-keygen -t ed25519 -C "label" -f ~/.ssh/keyname

# Load the private key into the SSH agent
ssh-add ~/.ssh/keyname

# Test authentication with GitHub
ssh -T git@github.com

Docker#

Docker packages your application and all its dependencies into a self-contained unit called a container. The container runs identically on any machine — your laptop, Railway, anywhere.

Key concepts:

  • Image — the blueprint. Built once from a Dockerfile.

  • Container — a running instance of an image.

  • Dockerfile — a recipe that describes how to build the image step by step.

  • Registry — a place to store and share images (e.g., Docker Hub).

The workflow is: write Dockerfile → build image locally → push to registry → server pulls and runs it.

# Build an image tagged as geoassistant-api
docker buildx build -t geoassistant-api .

# Run it locally, loading env vars from .env
docker run -p 8000:8000 --env-file .env geoassistant-api

# Push to Docker Hub
docker push username/geoassistant-api

DNS and Subdomains#

DNS (Domain Name System) translates human-readable domain names into IP addresses. Cloudflare manages DNS for geoassistant.org.

A CNAME record points one domain name to another:

app.geoassistant.org → 6c01159d13411acd.vercel-dns-017.com

This tells the internet: “when someone asks for app.geoassistant.org, send them to Vercel.”

The ``/etc/hosts`` file (or C:\Windows\System32\drivers\etc\hosts on Windows) overrides DNS locally. This is how your local dev environment uses custom domain names like geoassistant.localhost without those domains actually existing on the internet.

JWT and Auth0#

JWT (JSON Web Token) is a compact, self-contained token that proves a user’s identity. Auth0 issues JWTs after login. Your API verifies them.

A JWT has three parts separated by dots:

  • Header — algorithm used (e.g., RS256)

  • Payload — claims about the user (name, email, sub)

  • Signature — cryptographic proof that Auth0 signed it

Your API verifies the signature using Auth0’s public keys, fetched from the JWKS endpoint:

https://{AUTH0_DOMAIN}/.well-known/jwks.json

This prevents anyone from forging tokens — only Auth0’s private key can produce a valid signature.

CORS#

CORS (Cross-Origin Resource Sharing) is a browser security mechanism that blocks requests from one domain to another unless the server explicitly allows it.

When your SaaS app at app.geoassistant.org makes a request to api.geoassistant.org, the browser first asks the API “do you accept requests from this origin?” The API responds with the allowed origins in its headers.

In FastAPI:

app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://app.geoassistant.org"],
    allow_credentials=True,
)

allow_credentials=True is required for cookies to be sent cross-origin.