Development Environment Setup

This tutorial guides you through setting up a containerised development environment using Docker, Visual Studio Code, .NET MAUI, and PostgreSQL. By the end, you will have a reproducible development environment that includes everything needed for mobile app development with database support.

Architecture Overview

The development environment uses Docker to provide a consistent build environment across all platforms. The Android emulator runs on your host machine (for performance and GPU access), while the container handles building and deploying your app.

Fig. 1. Architecture overview
Fig. 1. Architecture overview

1. Install Docker Desktop

Docker Desktop provides a graphical interface for managing containers and includes everything needed to run containerised applications on your computer.

Windows Mac Linux

Download Docker Desktop from the Docker website and run the installer.

Warning

Docker Desktop on Windows requires WSL 2 (Windows Subsystem for Linux). If you don’t have it installed, the Docker installer will prompt you to enable it. You may need to restart your computer during this process.

After installation, Docker Desktop should start automatically. You can verify it’s running by looking for the Docker whale icon in your system tray.

Fig. 2. Docker Desktop running on Windows
Fig. 2. Docker Desktop running on Windows

Download Docker Desktop from the Docker website.

Note

Make sure you download the correct version for your Mac:

  • Apple Silicon (M1, M2, M3, M4 chips): Download the “Apple Silicon” version
  • Intel: Download the “Intel chip” version

You can check your chip type by clicking the Apple menu and selecting “About This Mac”.

Open the downloaded .dmg file and drag the Docker icon to your Applications folder. Launch Docker from your Applications folder.

Fig. 3. Docker Desktop running on Mac
Fig. 3. Docker Desktop running on Mac

On most Linux distributions, you can install Docker Engine using your package manager. For Ubuntu/Debian:

1
2
3
sudo apt-get update
sudo apt-get install docker.io docker-compose-v2
sudo usermod -aG docker $USER

Log out and back in for the group change to take effect. Alternatively, you can install Docker Desktop for Linux if you prefer a graphical interface.

Verify Docker is installed correctly by opening a terminal and running:

1
docker --version

You should see output similar to Docker version 24.0.0 or higher.

2. Install Visual Studio Code and Extensions

Follow the installation instructions for your operating system on the VSCode website.

Once VSCode is installed, you need to add the following extensions:

Extension Extension ID Purpose
Dev Containers ms-vscode-remote.remote-containers Develop inside Docker containers
Docker ms-azuretools.vscode-docker Manage Docker containers and images
Database Client cweijan.vscode-postgresql-client2 Connect to and query PostgreSQL databases
.NET MAUI dotnettools.dotnet-maui .NET MAUI development support
AVD Manager toroxx.vscode-avdmanager Manage Android Virtual Devices

To install an extension, open VSCode and press Ctrl+Shift+X (Windows/Linux) or Cmd+Shift+X (Mac) to open the Extensions panel. Search for each extension by name and click Install.

Fig. 4. Required VSCode extensions installed
Fig. 4. Required VSCode extensions installed

3. Create the Project Structure

Create a new project folder with the required subdirectory for the dev container configuration.

Windows Mac/Linux

Open a command prompt and run:

1
2
3
mkdir MauiDevProject
cd MauiDevProject
mkdir .devcontainer

Open a terminal and run:

1
2
mkdir -p MauiDevProject/.devcontainer
cd MauiDevProject

Open the MauiDevProject folder in VSCode using File > Open Folder… or by running code . from the terminal while in the project directory.

Your project structure should look like this:

1
2
MauiDevProject/
└── .devcontainer/

4. Create the Dockerfile

The Dockerfile defines the development container with all the tools needed for .NET MAUI development, including the .NET SDK, MAUI workloads, Java JDK, and Android SDK.

Create a file called Dockerfile in the project root directory with the following content:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
FROM mcr.microsoft.com/devcontainers/base:ubuntu

# Install .NET SDK 9
RUN wget https://dot.net/v1/dotnet-install.sh -O dotnet-install.sh \
    && chmod +x dotnet-install.sh \
    && ./dotnet-install.sh --channel 9.0 --install-dir /usr/share/dotnet \
    && ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet \
    && rm dotnet-install.sh

# Install MAUI workloads
RUN dotnet workload install maui

# Install Java JDK (required for Android SDK)
RUN apt-get update && apt-get install -y openjdk-17-jdk

# Install Android SDK command-line tools
ENV ANDROID_HOME=/opt/android-sdk
ENV JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
RUN mkdir -p ${ANDROID_HOME}/cmdline-tools \
    && wget https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip \
    && unzip commandlinetools-linux-*.zip -d ${ANDROID_HOME}/cmdline-tools \
    && mv ${ANDROID_HOME}/cmdline-tools/cmdline-tools ${ANDROID_HOME}/cmdline-tools/latest \
    && rm commandlinetools-linux-*.zip

# Accept licenses and install platform tools
RUN yes | ${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager --licenses \
    && ${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager "platform-tools" "platforms;android-34"

ENV PATH="${PATH}:${ANDROID_HOME}/cmdline-tools/latest/bin:${ANDROID_HOME}/platform-tools"

Note

Understanding the Dockerfile:

  • .NET SDK 9 - The latest .NET SDK for building MAUI applications
  • MAUI workloads - Required components for cross-platform mobile development
  • Java JDK 17 - Required by the Android SDK tools
  • Android SDK - Command-line tools for building and deploying Android apps
  • Platform tools - ADB and other utilities for communicating with Android devices

5. Configure Docker Compose

Docker Compose allows you to define and run multi-container applications. We will create a configuration with two services: an application container for development and a PostgreSQL database container.

Create a file called docker-compose.yml in the project root directory with the following content:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    volumes:
      - .:/workspace:cached
    network_mode: service:db
    command: sleep infinity
    extra_hosts:
      - "host.docker.internal:host-gateway"

  db:
    image: postgres:16
    restart: always
    environment:
      POSTGRES_USER: dev_user
      POSTGRES_PASSWORD: dev_password
      POSTGRES_DB: devdb
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:

Note

Understanding the configuration:

  • build - Builds the container using our custom Dockerfile instead of a base image
  • network_mode: service:db - This connects the app container directly to the database container’s network. This means you can connect to PostgreSQL using localhost:5432 from within the app container.
  • extra_hosts - Allows the container to connect to services running on the host machine (such as the Android emulator) using the hostname host.docker.internal
  • volumes: postgres_data - This creates a named volume that persists your database data even when containers are stopped or removed.
  • command: sleep infinity - This keeps the app container running so VSCode can connect to it.
Fig. 5. Docker Compose architecture diagram
Fig. 5. Docker Compose architecture diagram

6. Configure the Dev Container

The dev container configuration tells VSCode how to use the Docker Compose setup for development.

Create a file called devcontainer.json inside the .devcontainer directory with the following content:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
    "name": "MAUI Dev Environment",
    "dockerComposeFile": ["../docker-compose.yml"],
    "service": "app",
    "workspaceFolder": "/workspace",
    "customizations": {
        "vscode": {
            "extensions": [
                "cweijan.vscode-postgresql-client2",
                "ms-dotnettools.dotnet-maui",
                "toroxx.vscode-avdmanager"
            ]
        }
    },
    "remoteUser": "root"
}

Note

Understanding the configuration:

  • dockerComposeFile - Points to your Docker Compose configuration
  • service - Specifies which container VSCode should connect to
  • workspaceFolder - The folder inside the container where your project files appear
  • customizations.vscode.extensions - Extensions to install automatically inside the container, including the PostgreSQL client, .NET MAUI tools, and AVD Manager

Your project structure should now look like this:

1
2
3
4
5
MauiDevProject/
├── .devcontainer/
│   └── devcontainer.json
├── docker-compose.yml
└── Dockerfile

7. Create a .gitignore File

Before initialising a git repository, create a .gitignore file to exclude sensitive and unnecessary files from version control.

Create a file called .gitignore in the project root directory:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# Environment files
.env
.env.local
.env.*.local

# IDE settings
.idea/
*.swp
*.swo
*~

# OS files
.DS_Store
Thumbs.db

# Docker volumes (if using bind mounts for data)
postgres_data/

# .NET build outputs
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/

# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates

Warning

Never commit database credentials or .env files containing passwords to version control. The credentials in this tutorial are for local development only.

8. Open the Project in the Container

Now you can open the project inside the development container.

  1. Press Ctrl+Shift+P (Windows/Linux) or Cmd+Shift+P (Mac) to open the command palette.
  2. Type “Reopen in Container” and select Dev Containers: Reopen in Container.
Fig. 6. Building the container
Fig. 6. Building the container

Warning

The first time you do this, Docker will build the custom image and download the required components (.NET SDK, MAUI workloads, Java JDK, Android SDK). This may take 10-15 minutes depending on your internet connection. Subsequent starts will be much faster as the image is cached locally.

Once the container is ready, you will see the project files in the Explorer panel, and the bottom-left corner of VSCode will show “Dev Container: MAUI Dev Environment”.

Fig. 7. VSCode running inside the container
Fig. 7. VSCode running inside the container

Verify the Development Tools

Open a terminal (Ctrl+ or Cmd+ ) and verify the development tools are installed:

1
2
3
4
5
6
7
8
# Check .NET version
dotnet --version

# Check MAUI workloads
dotnet workload list

# Check Android SDK
sdkmanager --version

You should see .NET 9.x, the MAUI workloads listed, and the Android SDK manager version.

Fig. 8. Terminal inside container showing configuration details
Fig. 8. Terminal inside container showing configuration details

9. Connect to PostgreSQL from VSCode

The Database Client extension allows you to browse and query your database directly from VSCode.

  1. Click on the Database icon in the Activity Bar on the left side of VSCode.
  2. Click the + button to create a new connection and select PostgreSQL.
  3. Enter the following connection details:
Parameter Value
Connection Name MauiDevProject
Group (leave blank)
Hostname localhost
Port 5432
Username dev_user
Password dev_password
Database devdb
Use SSL No
Fig. 9. PostgreSQL connection configuration
Fig. 9. PostgreSQL connection configuration

Note

Because the app container uses network_mode: service:db, it shares the network namespace with the database container. This means PostgreSQL is accessible at localhost:5432 from within the app container.

Once connected, you can expand the connection in the sidebar to see databases, schemas, and tables.

10. Verify the Database Setup

Let’s verify the database connection is working by creating a simple table and querying it.

Create a test table

Expand the devdb database in the sidebar until you can see the public > Query element. Click on the+ icon to create a new query called create_table. Enter the following SQL and execute it by clicking the Run button:

1
2
3
4
5
6
CREATE TABLE students (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(255) UNIQUE NOT NULL,
    enrolled_date DATE DEFAULT CURRENT_DATE
);
Fig. 10. Creating a test table in PostgreSQL
Fig. 10. Creating a test table in PostgreSQL

Insert sample data

Run the following SQL to add some test records:

1
2
3
4
INSERT INTO students (name, email) VALUES
    ('Alice Smith', 'alice@example.com'),
    ('Bob Jones', 'bob@example.com'),
    ('Charlie Brown', 'charlie@example.com');

Query the data

Run a SELECT query to verify the data was inserted:

1
SELECT * FROM students;
Fig. 11. Query results showing test data
Fig. 11. Query results showing test data

You should see three rows returned with the student data you inserted.

Clean up (optional)

If you want to remove the test table:

1
DROP TABLE students;

11. Android Emulator Setup (Host Machine)

The Android emulator runs on your host machine rather than inside the container. This provides better performance and access to hardware acceleration.

Alternative: Android Studio

If you prefer a graphical interface for managing virtual devices, you can install Android Studio instead of following the command-line approach below. Android Studio includes the SDK Manager and AVD Manager with a GUI.

Install Android SDK Command-Line Tools

Download the “Command line tools only” package from the Android developer website and extract it.

Windows Mac/Linux

  1. Create the directory structure: C:\Android\cmdline-tools\latest
  2. Extract the downloaded zip and move the bin and lib folders into latest

Warning

You must create the folder named latest and put the bin and lib folders inside it, or the tools will error out.

Set environment variables (search for “Environment Variables” in the Start menu):

Variable Value
ANDROID_HOME C:\Android

Add to your PATH:

  • C:\Android\cmdline-tools\latest\bin
  • C:\Android\emulator
  • C:\Android\platform-tools

Close and reopen any terminal windows for changes to take effect.

  1. Create the directory structure: ~/Android/cmdline-tools/latest
  2. Extract the downloaded zip and move the bin and lib folders into latest
1
2
3
4
mkdir -p ~/Android/cmdline-tools/latest
cd ~/Downloads
unzip commandlinetools-*_latest.zip
mv cmdline-tools/* ~/Android/cmdline-tools/latest/

Warning

You must create the folder named latest and put the bin and lib folders inside it, or the tools will error out.

Add these lines to your shell profile (~/.zshrc for Mac, ~/.bashrc for Linux):

1
2
export ANDROID_HOME=$HOME/Android
export PATH=$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/emulator:$ANDROID_HOME/platform-tools

Apply the changes:

1
source ~/.zshrc  # or source ~/.bashrc on Linux

Verify the installation:

1
sdkmanager --version

Install Emulator and System Image

Accept the Android SDK licenses:

1
sdkmanager --licenses

Type y to accept each license when prompted.

Install the emulator, platform tools, and a system image. Choose the appropriate image for your machine’s architecture:

Windows / Intel Mac / Linux Apple Silicon Mac

1
sdkmanager "emulator" "platform-tools" "platforms;android-34" "system-images;android-34;google_apis;x86_64"
1
sdkmanager "emulator" "platform-tools" "platforms;android-34" "system-images;android-34;google_apis;arm64-v8a"

Note

On Apple Silicon Macs, the ARM64 image runs natively without emulation, providing significantly better performance.

Create an Android Virtual Device (AVD)

Create an AVD using the Pixel 9 Pro device profile:

Windows / Intel Mac / Linux Apple Silicon Mac

1
avdmanager create avd -n Pixel_9_Pro -k "system-images;android-34;google_apis;x86_64" -d 37
1
avdmanager create avd -n Pixel_9_Pro -k "system-images;android-34;google_apis;arm64-v8a" -d 37

Device IDs

The -d 37 flag specifies the Pixel 9 Pro device profile. To see all available device profiles, run: avdmanager list device

Verify the AVD was created:

1
avdmanager list avd

Start the Emulator

Launch the emulator with your AVD:

1
emulator -avd Pixel_9_Pro

Warning

The first boot may take several minutes as Android performs initial setup. Subsequent starts will be faster.

Leave the emulator running while you work with the development container.

Connect the Container to the Emulator

To deploy apps from the container to the emulator, you need to configure ADB networking.

On your host machine (in a separate terminal), start the ADB server in network mode:

1
2
adb kill-server
adb -a -P 5037 nodaemon server start

Leave this terminal open while developing.

Inside the container (in the VSCode terminal), configure ADB to connect to the host:

1
export ADB_SERVER_SOCKET=tcp:host.docker.internal:5037

Persistent Configuration

To make this setting persistent, add the export command to ~/.bashrc inside the container, or add it to your devcontainer.json post-start command.

Verify the connection:

1
adb devices

You should see output similar to:

1
2
List of devices attached
emulator-5554	device

If the emulator appears in the list, you’re ready to deploy apps from the container.

12. Create and Run a MAUI Application

With the development container running and the emulator connected, you can create and deploy a .NET MAUI application.

Create a new MAUI project

Inside the container terminal, create a new MAUI project:

1
2
cd /workspace
dotnet new maui -n HelloMaui

This creates a new directory called HelloMaui containing a default MAUI application with a simple counter interface.

Build for Android

Navigate to the project directory and build for Android:

1
2
cd HelloMaui
dotnet build -f net9.0-android

Target Framework

The -f net9.0-android flag specifies the Android target framework. MAUI projects can target multiple platforms (Android, iOS, Windows, Mac), and this flag ensures you build specifically for Android.

The first build downloads additional dependencies and may take a few minutes. Subsequent builds will be faster.

Deploy to the Emulator

Ensure your emulator is running and the ADB connection is established (check with adb devices), then deploy and run the application:

1
dotnet build -f net9.0-android -t:Install

The -t:Install target builds the app and installs it on the connected emulator. After installation completes, you can find the app in the emulator’s app drawer.

Alternatively, to build, install, and automatically launch the app:

1
dotnet build -f net9.0-android -t:Run

Warning

If the deployment fails with “no devices/emulators found”, verify that:

  • The emulator is running on your host machine
  • The ADB server is started with adb -a -P 5037 nodaemon server start on the host
  • The container has ADB_SERVER_SOCKET set correctly
  • adb devices shows the emulator in the container

Verify the Application

The default MAUI app displays a “Hello, World!” message and a button that counts clicks. Tap the button to verify the app is running correctly.

Fig. 12. Default MAUI application running on the Android emulator
Fig. 12. Default MAUI application running on the Android emulator

Hot Reload (Optional)

For faster development iteration, you can use Hot Reload to see changes without rebuilding:

1
dotnet watch run -f net9.0-android

Note

Hot Reload works best for XAML and minor code changes. Structural changes to your app may still require a full rebuild.

13. Managing Your Development Environment

Starting and stopping

To stop the containers: Close VSCode or use the command palette (Ctrl+Shift+P / Cmd+Shift+P) and select Dev Containers: Reopen Folder Locally.

You can also stop containers from the terminal:

1
docker compose down

To start the containers again: Open the project in VSCode and select Dev Containers: Reopen in Container from the command palette.

Returning to local development

To switch back to developing outside the container, use the command palette and select Dev Containers: Reopen Folder Locally. Your files remain on your local machine and are always accessible.

Persistent data

The PostgreSQL data is stored in a Docker volume called postgres_data. This means your database contents persist across container restarts. To completely reset the database, you would need to remove this volume:

1
docker volume rm mauidevproject_postgres_data

Warning

Removing the volume deletes all data in the database. Only do this if you want to start fresh.

Rebuilding the container

If you need to update the development tools or modify the Dockerfile, rebuild the container:

  1. Open the command palette (Ctrl+Shift+P / Cmd+Shift+P)
  2. Select Dev Containers: Rebuild Container

This will rebuild the Docker image with any changes you’ve made to the Dockerfile.

Troubleshooting

Problem Solution
Docker not running Start Docker Desktop and wait for it to fully initialise
Port 5432 already in use Stop any local PostgreSQL instance or change the port in docker-compose.yml
Container fails to start Check Docker Desktop logs or run docker compose logs from the project directory
Cannot connect to database Verify the containers are running with docker compose ps
Extensions not loading Rebuild the container: Command Palette > Dev Containers: Rebuild Container
.NET SDK not found Rebuild the container to ensure the Dockerfile completed successfully
ADB cannot connect to emulator See the ADB troubleshooting section in the MAUI tutorial

Useful Docker commands

Command Description
docker compose ps List running containers for this project
docker compose logs View container logs
docker compose logs db View only database container logs
docker compose logs app View only app container logs
docker compose down Stop and remove containers
docker compose down -v Stop containers and remove volumes (deletes data)
docker compose build --no-cache Rebuild the image from scratch
docker volume ls List all Docker volumes

This site uses Just the Docs, a documentation theme for Jekyll.