WSL Full-Stack Development: FastAPI, PostgreSQL, Redis and Docker
Windows Subsystem for Linux (WSL) provides a practical Linux development environment directly inside Windows. For full-stack development, it can combine Linux-native tooling with Windows applications such as Visual Studio Code and Docker Desktop.
This guide builds a small technical-blog application environment using FastAPI, PostgreSQL, Redis, Vue 3, Docker Compose, and VS Code Remote - WSL.
The goal is not simply to get the application running. The workflow also establishes useful habits for WSL development, including keeping source code on the Linux filesystem, isolating Python dependencies, separating application services, and troubleshooting common WSL2 networking and filesystem issues.
🏗️ Project Architecture #
The example application uses the following stack:
| Layer | Technology |
|---|---|
| Backend | Python + FastAPI |
| Database | PostgreSQL |
| Cache | Redis |
| Frontend | HTML or Vue 3 + Vite |
| Containerization | Docker Compose |
| Development Environment | VS Code Remote - WSL |
The architecture can start as a native WSL development environment and later transition individual services or the complete application to containers.
This makes the project useful as both a learning exercise and a foundation for larger full-stack applications.
📁 Step 1: Create the Project in the Linux Filesystem #
One of the most important WSL development practices is choosing the correct location for your source tree.
For Linux-heavy workloads, keep projects inside the WSL filesystem—for example, under ~/projects—rather than directly under /mnt/c/.
Crossing between the Windows and Linux filesystems repeatedly can introduce filesystem overhead, particularly for projects containing large numbers of small files such as Python environments, JavaScript dependencies, or build artifacts.
Create the project structure:
mkdir -p ~/projects/blog-app/{app,tests,static,templates}
cd ~/projects/blog-appCreate an isolated Python environment:
python3 -m venv .venv
source .venv/bin/activateInstall the initial backend dependencies:
pip install fastapi uvicorn sqlalchemy psycopg2-binary redis pydantic python-multipartFreeze the environment:
pip freeze > requirements.txtFor a production project, it is generally preferable to manage dependencies with a dedicated project tool such as uv, Poetry, or a pinned requirements workflow. The important principle is that the project’s Python dependencies should remain isolated from the system Python installation.
🐘 Step 2: Install and Configure PostgreSQL #
PostgreSQL provides the application’s persistent relational database.
Install the server and supporting packages:
sudo apt update
sudo apt install postgresql postgresql-contrib -yStart PostgreSQL:
sudo service postgresql startCreate a dedicated application user:
sudo -u postgres createuser blog_user -PCreate the application database and assign ownership:
sudo -u postgres createdb blog_db -O blog_userVerify that the database accepts connections:
psql -U blog_user -d blog_db -h localhostUsing a dedicated database user is preferable to developing against the PostgreSQL superuser. It more closely resembles the privilege boundaries you should maintain in a deployed environment.
For local development, credentials should also be stored outside source code, typically through environment variables or a local .env file that is excluded from version control.
⚡ Step 3: Add Redis for Caching #
Redis can provide a fast in-memory data layer for frequently accessed information.
Install Redis:
sudo apt install redis-server -yStart the service:
sudo service redis-server startVerify that Redis is responding:
redis-cli pingA healthy instance should return:
PONGThe Python redis package can then be used to integrate Redis with FastAPI.
For asynchronous applications, use the current async interfaces provided by redis-py. Older examples based on the separate aioredis package may be outdated depending on the version of the Python ecosystem you are using.
Typical caching candidates include:
- Frequently requested blog posts
- Session or authentication data
- Computed API responses
- Rate-limiting state
- Temporary application data
Redis should complement PostgreSQL rather than replace it as the system of record.
🚀 Step 4: Build the FastAPI Backend #
Create a minimal app/main.py:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello, WSL Full-Stack!"}Run the development server:
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000The application should now be accessible from Windows through:
http://localhost:8000FastAPI also provides interactive API documentation by default. Once the server is running, the Swagger UI is typically available at:
http://localhost:8000/docsBinding to 0.0.0.0 is useful in WSL development when you need the service to be reachable beyond the loopback interface inside the Linux environment.
🎨 Step 5: Add a Vue 3 Frontend #
For a modern frontend, Vite can generate a Vue 3 project quickly.
From the project directory:
npm create vite@latest frontend -- --template vue
cd frontend
npm install
npm run devThe Vite development server will provide the frontend on its assigned development port.
The frontend can communicate with FastAPI through the backend’s port 8000.
For example, a development architecture might look like:
Windows
│
├── Browser
│ ├── Vue 3 + Vite
│ │ │
│ │ └── HTTP/API requests
│ │ │
│ └──────── FastAPI :8000
│ │
│ ┌──────┴──────┐
│ │ │
│ PostgreSQL Redis
│ :5432 :6379As the application grows, configure the frontend API endpoint through environment-specific configuration rather than hard-coding development addresses throughout the source tree.
🐳 Step 6: Containerize Supporting Services #
Docker Desktop can integrate with WSL2 and provide a convenient way to run infrastructure services consistently.
A minimal docker-compose.yml can define PostgreSQL and Redis:
services:
postgres:
image: postgres:15
environment:
POSTGRES_USER: blog_user
POSTGRES_PASSWORD: password
POSTGRES_DB: blog_db
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
redis:
image: redis:7
ports:
- "6379:6379"
volumes:
pgdata:Start the services:
docker compose up -dModern Docker Compose installations use the docker compose subcommand rather than requiring the older standalone docker-compose command.
Check the running containers:
docker compose psView service logs when debugging:
docker compose logs postgres
docker compose logs redisUsing containers for PostgreSQL and Redis while running FastAPI and the frontend directly in WSL is a practical intermediate architecture. It provides reproducible infrastructure without requiring every development process to run inside a container.
🔧 Step 7: Troubleshoot Common WSL Problems #
A productive WSL environment depends as much on understanding its boundaries as on knowing individual commands.
Slow Filesystem Performance #
For Linux-oriented projects, avoid keeping the primary source tree under:
/mnt/c/Prefer:
~/projects/This is particularly important for projects with large dependency trees, frequent file watching, or heavy build activity.
Node.js projects are a common example because node_modules can contain thousands of files.
Port Conflicts #
When a service fails to start because its port is already occupied, identify the process using it.
For example:
lsof -i :8000For broader socket inspection:
ss -ltnpYou can then stop the conflicting service or select another application port.
Docker and WSL Integration #
Verify that Docker is available from the WSL distribution:
docker infoIf the command fails, check Docker Desktop’s WSL integration settings and confirm that the relevant Linux distribution has been enabled.
The exact Docker Desktop UI can change between releases, so troubleshooting should begin by verifying the Docker daemon connection rather than assuming a particular configuration.
Python Environment Isolation #
Always verify that the expected virtual environment is active before installing packages or running Python applications:
source .venv/bin/activateThen check:
which python
python --versionThe Python executable should resolve to the project’s .venv rather than the system interpreter.
PostgreSQL and Redis Startup #
If services were installed directly inside WSL and are not configured to start automatically, they may need to be started manually:
sudo service postgresql start
sudo service redis-server startFor containerized services, use Docker Compose instead:
docker compose up -dAvoid accidentally running two copies of the same infrastructure service—for example, one native PostgreSQL instance and another PostgreSQL container both attempting to bind to port 5432.
🧪 Step 8: Build a Repeatable Troubleshooting Workflow #
WSL problems become easier to diagnose when troubleshooting follows a consistent order.
Start with the application itself:
curl http://localhost:8000/Then verify the relevant service:
docker compose psCheck the listening ports:
ss -ltnpTest PostgreSQL:
psql -U blog_user -d blog_db -h localhostTest Redis:
redis-cli pingFor Dockerized services, inspect logs:
docker compose logs --tail=100 postgres
docker compose logs --tail=100 redisFor Python problems, verify the active interpreter and installed dependencies:
which python
pip listThis layered approach helps distinguish between application errors, service failures, networking problems, and environment configuration issues instead of treating every failure as a generic WSL problem.
🧑💻 Use VS Code Remote - WSL for Development #
Visual Studio Code’s Remote - WSL workflow provides a convenient bridge between Windows and the Linux development environment.
Open the project from WSL:
cd ~/projects/blog-app
code .When Remote - WSL is configured correctly, VS Code runs its development-side components inside the Linux environment while the graphical editor remains a Windows application.
This provides several advantages:
- Linux-native Python and Node.js tooling
- Direct access to the WSL filesystem
- Integrated terminal sessions
- Debugging inside the Linux environment
- Language servers running against the correct runtime
- Convenient Windows desktop integration
The important principle is consistency: the editor, terminal, Python environment, Node.js tooling, and application source should all point toward the same WSL development environment.
🔄 Native WSL vs. Containerized Development #
There is no requirement to choose exclusively between native WSL development and Docker.
A hybrid workflow is often practical:
Windows
│
└── WSL2
│
├── Source code
│ └── ~/projects/blog-app
│
├── FastAPI
│ └── Python virtual environment
│
├── Vue / Vite
│ └── Node.js
│
└── Docker Compose
├── PostgreSQL
└── RedisThis approach provides fast source-code access while keeping infrastructure services reproducible.
As the application matures, the backend and frontend can also be containerized, allowing development and deployment environments to converge further.
📚 Build a Personal WSL Troubleshooting Knowledge Base #
A useful outcome of a WSL project is not just the finished application but a reusable record of the problems encountered along the way.
Document issues such as:
- Filesystem performance problems
- Port conflicts
- DNS or networking failures
- Docker integration errors
- Python environment problems
- PostgreSQL authentication failures
- Redis connection errors
- Frontend-to-backend CORS issues
- File-permission problems
- Service startup failures
For each problem, record four things:
- Symptom — What failed?
- Diagnosis — Which commands or observations identified the cause?
- Fix — What resolved the problem?
- Prevention — What configuration or practice prevents recurrence?
Over time, this becomes a practical WSL troubleshooting reference rather than a collection of disconnected commands.
🚀 Conclusion: Turn WSL Into a Reusable Full-Stack Environment #
A full-stack project is an effective way to move beyond basic WSL experimentation and develop practical Linux-on-Windows skills.
The example architecture combines:
- FastAPI for the backend API
- PostgreSQL for persistent data
- Redis for caching and fast transient state
- Vue 3 and Vite for the frontend
- Docker Compose for infrastructure services
- VS Code Remote - WSL for development
More importantly, the workflow introduces the operational habits required for reliable WSL development.
Keep Linux-heavy projects inside the WSL filesystem, isolate language dependencies, avoid port collisions, understand the boundary between WSL and Windows networking, verify Docker integration, and troubleshoot each layer independently.
Once these patterns become familiar, WSL stops being merely a compatibility layer and becomes a practical Linux development environment capable of supporting complete applications—from local source code and development servers to databases, caches, containers, and reproducible troubleshooting workflows.