Deploying a Flask application to a server with full internet access is straightforward: install Python, run pip install -r requirements.txt, and you are done. But what happens when the target server has no outbound internet connection — sometimes called an air-gapped or offline server? This scenario is common in regulated industries, corporate data centres, and high-security environments. Without a plan, pip will simply fail to resolve packages, and your deployment will stall.
Why Servers Are Sometimes Offline
Network-restricted servers exist for legitimate reasons:
- Security policy: Preventing outbound connections reduces the attack surface.
- Compliance: Regulations in finance, healthcare, and government often require isolated environments.
- Stability: Ensuring that a production environment never pulls unexpected package versions from the internet.
Whatever the reason, you need to bring all dependencies with your deployment artefact rather than fetching them at install time.
Core Strategy: Bundle Your Dependencies
The fundamental approach is to download all Python packages on a machine that does have internet access, bundle them alongside your application code, transfer the bundle to the offline server, and install from the local copy. The steps below walk through this process.
Step 1: Pin All Dependencies
Before anything else, generate a fully pinned requirements.txt from your working development or CI environment. Use pip freeze to capture exact versions:
pip freeze > requirements.txtCode language: CSS (css)
Pinned versions are critical for offline deployments because you cannot let pip resolve the latest compatible version at install time — there is no network to query.
Step 2: Download Packages as Wheel Files
On an internet-connected machine that matches the target server’s Python version and OS architecture, download all packages as pre-built wheel (.whl) files into a local directory:
pip download -r requirements.txt -d ./wheelhouse
The -d flag tells pip to store the downloaded files in the ./wheelhouse directory rather than installing them. Wheel files are platform-specific, so the download machine should ideally run the same OS and CPU architecture as the target server (e.g., Linux x86-64). If there is a mismatch, you can add the --platform, --python-version, and --abi flags to pip download to target the correct platform explicitly.
Step 3: Bundle and Transfer
Archive your application code and the wheelhouse directory together:
tar -czf myapp_deploy.tar.gz myapp/ wheelhouse/ requirements.txt
Transfer the archive to the offline server using whatever mechanism is available — SCP over a bastion host, USB drive, internal file share, or a deployment tool such as Ansible, Fabric, or a CI/CD pipeline that has internal network access.
Step 4: Install from the Local Wheelhouse
On the offline server, extract the archive and install packages using --no-index and --find-links to prevent pip from reaching out to PyPI:
pip install \
--no-index \
--find-links=./wheelhouse \
-r requirements.txt
--no-indexdisables all index lookups, including PyPI.--find-linkstells pip to look in the specified local directory for package files.
If every required package is present in the wheelhouse, the installation will succeed without any network calls.
Setting Up a Private PyPI Mirror (for Scale)
If you manage many Flask microservices — or update them frequently — maintaining per-application wheelhouse bundles quickly becomes tedious. A better long-term solution is to host an internal PyPI mirror on a server that is reachable from your offline servers over the internal network.
Tools commonly used for this include:
- devpi — a full-featured PyPI server and proxy with caching.
- pypiserver — a lightweight, easy-to-deploy PyPI-compatible server.
- Nexus Repository Manager or Artifactory — enterprise artefact repositories that support PyPI among other formats.
Once your internal mirror is populated, configure pip on all offline servers to use it by default, either via the PIP_INDEX_URL environment variable or a pip.conf file:
# /etc/pip.conf or ~/.pip/pip.conf
[global]
index-url = http://internal-pypi.company.local/simple/
trusted-host = internal-pypi.company.localCode language: PHP (php)
Using Virtual Environments
Regardless of whether you use a wheelhouse or an internal mirror, always deploy Flask applications inside a Python virtual environment. This isolates the application’s dependencies from the system Python and from other applications running on the same host:
python3 -m venv /opt/myapp/venv
source /opt/myapp/venv/bin/activate
pip install --no-index --find-links=./wheelhouse -r requirements.txt
Containerised Deployments on Offline Servers
If your offline server runs Docker or another container runtime, you can build the Docker image on an internet-connected machine and transfer the image as a tar archive:
# On the build machine
docker build -t myapp:1.0 .
docker save myapp:1.0 | gzip > myapp_1.0.tar.gz
# Transfer myapp_1.0.tar.gz to the offline server, then:
docker load < myapp_1.0.tar.gz
docker run -d -p 5000:5000 myapp:1.0Code language: PHP (php)
This approach bundles the entire runtime environment — OS libraries, Python interpreter, and application code — into a single portable artefact, eliminating dependency concerns on the target host entirely.
Serving Flask in Production
Flask’s built-in development server is not suitable for production use. On an offline server you still need a production-grade WSGI server. Common choices include:
- Gunicorn — widely used, easy to configure, include it in
requirements.txtso it is bundled in your wheelhouse. - uWSGI — performant and feature-rich, though more complex to configure.
- Waitress — pure-Python, a good choice when compiling C extensions is not possible on the target platform.
Pair the WSGI server with a reverse proxy such as Nginx or Apache (installed via the system package manager from an internal mirror or pre-installed on the server image) to handle TLS termination, static files, and load balancing.
Checklist: Offline Flask Deployment
- ☑ Generate a fully pinned
requirements.txtwithpip freeze. - ☑ Run
pip downloadon a machine matching the target OS and Python version. - ☑ Include the WSGI server (e.g., Gunicorn) in
requirements.txt. - ☑ Bundle application code and the wheelhouse together.
- ☑ Transfer the bundle via an approved secure channel.
- ☑ Create a virtual environment on the target server.
- ☑ Install with
pip install --no-index --find-links=./wheelhouse -r requirements.txt. - ☑ Start the application with your WSGI server and manage it with systemd or supervisor.
- ☑ Configure a reverse proxy for production traffic.
Following this process ensures repeatable, network-independent deployments for Flask microservices, regardless of how locked down the target environment is.
This article is an independent explanation of offline Flask deployment techniques. It was prompted by a post originally published on the Theodo blog (November 2016).