From XAMPP to Docker: A Better Way to Develop PHP Applications

SimonSimon
17 min read

All code and resources for this post are available on GitHub. You can follow along by cloning the repository!

Last semester in my Computer Science studies, we built a small backend with PHP. To get started, we used XAMPP — just like I did more than 10 years ago when I first learned web development.
That got me thinking: Is XAMPP still the right tool in 2025, or is it time to switch to Docker?

With tools like Docker becoming standard in professional environments, I wanted to take a step back and compare the two. In this post, I’ll share my thoughts on XAMPP vs Docker - and why I believe Docker offers a more modern and efficient way to develop PHP applications today.

What Is XAMPP And Why Isn’t Enough Anymore

XAMPP is a PHP development environment introduced back in 2002. It made it extremely easy to set up an Apache distribution that includes MariaDB, PHP, and Perl. Thanks to its simplicity, it quickly gained popularity among beginners and hobby developers. I used it myself when I first started learning web development.

However, while XAMPP is still functional today, it no longer meets the needs of modern software development. Here are a few reasons why:

  • Lack of environment consistency: XAMPP runs natively on the operating system, which can lead to differences between development and production environments, especially when working in a team or deploying to the cloud.

  • Poor isolation: If we're working on multiple projects with different PHP versions or configurations, managing them with XAMPP quickly becomes messy.

  • Limited scalability: XAMPP isn't designed for setting up microservices or integrating services like Redis, RabbitMQ, or custom databases, all common in modern web stacks.

  • Not ideal for CI/CD: XAMPP is meant for local development and doesn't integrate well into automated testing or deployment pipelines.

Why Docker Is a Better Fit for Modern PHP Development

All the limitations mentioned above can be addressed with Docker. Instead of installing services directly on the machine, Docker allows to define the entire development environment in code and run it in isolated containers. This brings several key advantages:

  • Consistency across environments: With Docker, the local setup can exactly match staging and production. If it works in a container, it works everywhere.

  • Project isolation: Each project can run in its own container with its own PHP version, extensions, and dependencies. This setup prevents conflicts and manual switching.

  • Infrastructure as code: With a simple Dockerfile and docker-compose.yml, your setup is fully documented, versioned, and reproducible. New team members can get started with just a docker compose up.

  • Scalability and flexibility: Need Redis, MongoDB, or a specific database version? Just add a service to the docker-compose.yml, no need for manual installations or config conflicts.

  • CI/CD integration: Docker containers can be used in automated pipelines to build, test, and deploy the application consistently and reliably.

Getting Started: Dockerizing a PHP Application

Let’s walk through a basic example to see how easy it is to set up a PHP project using Docker.
Here’s the simple project structure:

/root
├── compose.yaml
└── /src
    └── index.php

The src folder contains all your application code, such as PHP, CSS, or JavaScript files. This directory will be mounted into the container so that any code changes are immediately reflected without rebuilding the image.

index.php

The index.php is a basic HTML page with a bit of embedded PHP and Tailwind CSS for styling:

<!doctype html>
<html>
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="https://unpkg.com/@tailwindcss/browser@4"></script>
    <title>PHP Website</title>
</head>
<body class="max-w-4xl mx-auto p-12">
    <h1 class="text-4xl font-bold text-clifford mb-6">PHP Website</h1>
    <?php
        echo "<p>Text from PHP</p>";
    ?>
</body>
</html>

compose.yaml

With Docker, we can run this site without installing PHP or Apache directly on the machine. Just define the environment in a simple compose.yaml file:

services:
  php-env:
    image: php:8.4-apache
    volumes:
      - ./src:/var/www/html/
    ports:
      - "9001:80"

This setup does the following:

  • Uses the official PHP 8.4 image with Apache pre-installed.

  • Mounts your local src/ folder into the container’s web root (/var/www/html/).

  • Maps port 80 inside the container to port 9001 on the local machine.

Running the App

To run the application, simply open a terminal in the project root and run:

docker compose up --build

Then visit http://localhost:9001 in your browser—you’ll see your PHP page live, served through Apache in a Docker container:

Dockerizing a PHP Application with MariaDB

In the previous example, we ran a standalone PHP site in Docker. Now let’s take it further and connect our application to a MariaDB database using Docker Compose.
Here’s the updated project structure:

/root
├── .env
├── compose.yaml
├── Dockerfile
└── /src
    ├── index.php
    ├── db_connect.php
    └── db-init.sql
  • The src/ folder contains your PHP code and an SQL script to initialize the database.

  • The .env file stores environment variables (e.g., your database password).

  • The compose.yaml file defines all Docker services.

  • The Dockerfile builds a custom PHP container with the required extensions.

The .env file

In the .env file, we store sensitive or environment-specific values to keep them separate from our source code. Docker Compose automatically loads variables from this file, which we reference in compose.yaml and pass into the container environment.

DB_PASSWORD=dbpassword123

Dockerfile

The php:8.4-apache image is a good starting point, but it doesn’t come with the PHP extensions needed to connect to a MySQL or MariaDB database. To fix this, we create a custom Dockerfile where we install the missing extensions:

FROM php:8.4-apache

# Install necessary extensions
RUN docker-php-ext-install pdo pdo_mysql mysqli

compose.yaml

In the compose.yaml file we define three different services:

  1. The service db: This is the MariaDB server, for which we use the latest official mariadb image. For the environment variables, we set the database root password (which we defined earlier in the .env file) and the time zone. We also mount two volumes: db-vol, which is where the database data is stored persistently, and the db-init.sql script from our src folder, which automatically loads some sample data the first time we start the container.

  2. The service pma: The second service is pma, which stands for phpMyAdmin. This gives us a web interface to manage our MariaDB database directly in the browser. We expose it on port 6080 and link it to the db service so it can connect to the running database.

  3. The service php-env: The third service is php-env, which is our PHP environment with Apache. Here we use our custom Dockerfile that includes the required PHP extensions. We mount the ./src folder into /var/www/html/ so that the container serves our code automatically. We also pass the database password as an environment variable to make it available in the PHP application.

services:
  db:
    image: mariadb
    environment:
      - MARIADB_ROOT_PASSWORD=${DB_PASSWORD}
      - TZ=Europe/Zurich
    restart: always
    volumes:
      - db-vol:/var/lib/mysql
      - ./src/db-init.sql:/docker-entrypoint-initdb.d/db-init.sql:ro  # Mount SQL script

  pma:
    image: phpmyadmin
    environment:
      - PMA_HOST=db
    ports:
      - "6080:80"
    restart: on-failure:10
    depends_on:
      - db

  php-env:
    build: .
    volumes:
      - ./src:/var/www/html/
    ports:
      - 9001:80
    restart: always
    depends_on:
      - db
    environment:
      - DB_PASSWORD=${DB_PASSWORD}

volumes:
  db-vol:

The src/ folder

The src/ folder contains all our frontend and backend code—HTML, CSS, JavaScript, and PHP files. This is the directory we mount into our Docker container so it can serve our application.

index.php

In index.php, we connect to the database, fetch the first five users from the users table, and display them in an HTML table.

<?php
    require_once 'db_connect.php'; // Include the database connection

    // Fetch users with error handling
    $sql_query = "SELECT id, name, email, created_at FROM users LIMIT 5";
    try {
        $stmt = $pdo->query($sql_query);
        $users = $stmt->fetchAll(PDO::FETCH_ASSOC);
    } catch (PDOException $e) {
        $users = [];
    }
?>
<!doctype html>
<html>
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="https://unpkg.com/@tailwindcss/browser@4"></script>
    <title>PHP with MySQL</title>
</head>
<body class="max-w-4xl mx-auto p-12">
    <h1 class="text-4xl font-bold text-clifford mb-6">PHP with MySQL</h1>

    <?php if (!empty($users)): ?>
        <table class="w-full text-sm text-left text-gray-900 shadow mt-12">
            <thead class="text-xs text-gray-700 uppercase bg-gray-50">
                <tr>
                    <th scope="col" class="px-6 py-3">
                        ID
                    </th>
                    <th scope="col" class="px-6 py-3">
                        Name
                    </th>
                    <th scope="col" class="px-6 py-3">
                        Email
                    </th>
                    <th scope="col" class="px-6 py-3">
                        Created At
                    </th>
                </tr>
            </thead>
            <tbody>
                <?php foreach ($users as $user): ?>
                    <tr class="bg-white border-b border-gray-200">
                        <td class="px-6 py-3"><?php echo htmlspecialchars($user['id']); ?></td>
                        <td class="px-6 py-3"><?php echo htmlspecialchars($user['name']); ?></td>
                        <td class="px-6 py-3"><?php echo htmlspecialchars($user['email']); ?></td>
                        <td class="px-6 py-3"><?php echo htmlspecialchars($user['created_at']); ?></td>
                    </tr>
                <?php endforeach; ?>
            </tbody>
        </table>
    <?php else: ?>
        <p class="text-gray-700 text-lg">No users found.</p>
    <?php endif; ?>
</body>
</html>

db_connect.php

The db_connect.php script handles the connection to our MariaDB database. It uses the service name db from our compose.yaml file as the host and gets the password from the .env file using getenv(). If the connection fails, it shows a styled error message.

<?php
$host = 'db'; // The service name from docker-compose.yml
$dbname = 'my_database';
$username = 'root';
$password = getenv('DB_PASSWORD');

try {
    $pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8", $username, $password);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
    die("<p class='text-red-500 text-lg font-semibold'>Database connection failed. Please try again later:<br>" . $e->getMessage() ."</p>");
}
?>

db-init.sql

This is the initial SQL script that gets executed automatically when we create the MariaDB container for the first time. It creates the database, defines a users table, and inserts three sample users.

Because the script is mounted into the container through the compose.yaml file, MariaDB runs it automatically the first time the container is created - so the database is ready with data right away.

-- Create database if it doesn't exist
CREATE DATABASE IF NOT EXISTS my_database;
USE my_database;

-- Create a sample table
CREATE TABLE IF NOT EXISTS users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Insert sample data
INSERT INTO users (name, email) VALUES 
('Alice Smith', 'alice@example.com'),
('Bob Johnson', 'bob@example.com'),
('John Doe', 'john@example.com');

Running the App

To get everything running, just execute:

docker compose up --build

Then open http://localhost:9001 to view the PHP app, or http://localhost:6080 to access phpMyAdmin.

Dockerizing a PHP Application with MongoDB

In this example, we build a Dockerized PHP application that connects to a MongoDB database. This setup is slightly more complex than using a traditional SQL database like MySQL, mainly due to how MongoDB integrates with PHP.

One key difference is that the MongoDB extension for PHP is not part of the core PHP source. Instead, it must be installed separately using pecl install mongodb, and then enabled with docker-php-ext-enable mongodb. Additionally, we use the official MongoDB PHP library (mongodb/mongodb) to work with the database in our application code, which is installed using Composer.

To support both development and production workflows cleanly, the setup uses a multi-stage Dockerfile. The development stage allows for live code editing by mounting the local source folder, while the production stage creates a clean, standalone image with the source code and dependencies already baked in.

Because the development container mounts your local src/ directory into the container, it completely replaces the container’s internal source code — including the vendor/ folder that Composer generates. This means the vendor/ folder must exist locally when running in dev mode. To solve this, we provide a helper script, build-vendor.ps1, which builds the Docker image, extracts the vendor/ folder from the container, and copies it to the local src/ directory. You should run this script once after cloning the project and any time you change composer.json.

With this setup, you can develop and test your PHP + MongoDB application using live code reloads, and easily switch to a clean, reproducible production build when you're ready to deploy.

The project structure:

/root
├── mongo-init/
│   └── init.js                # JavaScript file to initialize MongoDB with seed data
│
├── src/
│   ├── composer.json          # Declares PHP dependencies
│   ├── db_connect.php         # Handles MongoDB connection
│   ├── index.php              # Main entry point of the PHP web application
│   └── vendor/                # Installed PHP libraries (created by build-vendor.ps1 in dev)
│
├── .dockerignore              # Excludes files/folders from the Docker build context
├── .env                       # Contains environment variables like DB credentials
├── build-vendor.ps1           # PowerShell script to extract vendor/ from image to local src/
├── compose.dev.yaml           # Docker Compose file for development (live code editing, volume mount)
├── compose.prod.yaml          # Docker Compose file for production (clean image, no volume mount)
├── Dockerfile                 # Multi-stage Dockerfile (composer-builder, dev, prod targets)

The .env file

In the .env file, we store the db username and the db password.

DB_PASSWORD=dbpassword123
DB_USER=root

Dockerfile

The Dockerfile is a multi-stage build with three clearly separated stages:

  1. Composer Build Stage: This stage installs PHP dependencies using Composer. It’s used to generate the vendor/ folder based on the composer.json file.

  2. Development Stage: This stage is optimized for development. It supports live code updates via volume mounting (./src:/var/www/html), allowing changes to be reflected without rebuilding the container. Because the volume mount overwrites the container’s internal code, the vendor/ folder must also exist locally in ./src. This can be generated using the build-vendor.ps1 script.

  3. Production Stage: This final stage is optimized for deployment. It copies the complete source code and vendor/ folder into the container, creating a clean, self-contained image without volume mounts or development tools.

# ----------------------------------------
# Stage 1: Composer build stage (shared)
# ----------------------------------------
FROM php:8.4-cli AS composer-builder

RUN apt-get update && apt-get install -y \
    unzip curl git zip libssl-dev libcurl4-openssl-dev pkg-config \
    && pecl install mongodb \
    && docker-php-ext-enable mongodb

# Install Composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

# Copy composer config and install dependencies
WORKDIR /app
COPY ./src/composer.json ./
RUN composer install --no-dev --no-interaction --optimize-autoloader


# ----------------------------------------
# Stage 2: Development container
# ----------------------------------------
FROM php:8.4-apache AS dev

RUN apt-get update && apt-get install -y \
    unzip curl git zip libssl-dev libcurl4-openssl-dev pkg-config \
    && pecl install mongodb \
    && docker-php-ext-enable mongodb

RUN a2enmod rewrite

WORKDIR /var/www/html

# Copy source code for dev (can be overridden with volume)
COPY ./src/ /var/www/html/
COPY --from=composer-builder /app/vendor /var/www/html/vendor/


# ----------------------------------------
# Stage 3: Production container
# ----------------------------------------
FROM php:8.4-apache AS prod

RUN apt-get update && apt-get install -y \
    unzip curl git zip libssl-dev libcurl4-openssl-dev pkg-config \
    && pecl install mongodb \
    && docker-php-ext-enable mongodb

RUN a2enmod rewrite

WORKDIR /var/www/html

# Copy source and vendor for final deployable image
COPY ./src/ /var/www/html/
COPY --from=composer-builder /app/vendor /var/www/html/vendor/

build-vendor.ps1

The build-vendor.ps1 script is used to prepare the development environment by generating the vendor/ folder locally inside the src/ directory. This is necessary because the development container mounts the local src/ folder, which must already include the dependencies.
You should run this script the first time after cloning the project and whenever composer.json is updated.

# Set image and build target
$imageName = "php-mongodb-dev"
$buildTarget = "dev"

Write-Host "Building Docker image with target: $buildTarget..."
docker build --target=$buildTarget -t $imageName .

Write-Host "Creating temporary container from image..."
$containerID = docker create $imageName

Write-Host "Cleaning up any existing src/vendor/..."
if (Test-Path "./src/vendor") {
    Remove-Item -Recurse -Force "./src/vendor"
}

Write-Host "Copying vendor/ from container to ./src/vendor..."
docker cp ${containerID}:/var/www/html/vendor ./src/vendor

Write-Host "Cleaning up temporary container..."
docker rm $containerID

Write-Host "Done! You can now run: docker compose -f compose.dev.yaml up"

compose.dev.yaml

In the compose.yaml file we define three different services:

  1. The service php-apache: The first service is php-apache, which sets up the PHP environment with Apache. Here, we use the dev build from the custom Dockerfile that includes the required PHP extensions, such as the MongoDB extension. We mount the ./src folder into /var/www/html/ so that the container automatically serves the current version of our code. We also load the .env file to make the environment variables available inside the PHP application.

  2. The service db: The second service is mongo, which provides the MongoDB database. We use the official mongo image and configure it using environment variables for the root username and password, which are defined in the .env file. We also mount two volumes: one named volume called mongo-data-dev to store the database data persistently, and the mongo-init folder from our project, which contains an initialization script. This script runs the first time the container starts and loads some sample data into the database.

  3. The service mongo-express: The third service is mongo-express, which provides a web interface to manage our MongoDB database directly in the browser. It is exposed on port 8081 and is linked to the mongo service so that it can connect to the running database. The credentials for accessing this interface are also provided via the .env file, using the same database username and password.

services:
  php-apache:
    build:
      context: .
      dockerfile: Dockerfile
      target: dev
    image: php-mongodb-dev
    ports:
      - "8080:80"
    volumes:
      - ./src:/var/www/html
    depends_on:
      - mongo
    env_file:
      - .env

  mongo:
    image: mongo
    ports:
      - "27017:27017"
    environment:
      MONGO_INITDB_ROOT_USERNAME: ${DB_USER}
      MONGO_INITDB_ROOT_PASSWORD: ${DB_PASSWORD}
    volumes:
      - ./mongo-init:/docker-entrypoint-initdb.d
      - mongo-data-dev:/data/db

  mongo-express:
    image: docker.io/library/mongo-express:1.0.2-20-alpine3.19
    ports:
      - "8081:8081"
    environment:
      ME_CONFIG_MONGODB_ADMINUSERNAME: ${DB_USER}
      ME_CONFIG_MONGODB_ADMINPASSWORD: ${DB_PASSWORD}
      ME_CONFIG_MONGODB_SERVER: mongo
      ME_CONFIG_BASICAUTH_USERNAME: ${DB_USER}
      ME_CONFIG_BASICAUTH_PASSWORD: ${DB_PASSWORD}
    depends_on:
      - mongo

volumes:
  mongo-data-dev:

compose.prod.yaml

The compose.prod.yaml file is very similar to the one we use for development, but it's made for production. Instead of using the dev build, we use the prod build from our Dockerfile, which creates a clean image with everything needed to run the app. In production, we don’t mount the source code folder into the container. Instead, the source code is copied into the image during the build. We also use a separate volume called mongo-data-prod to store the database data, so it stays safe and doesn't mix with the development data.

services:
  php-apache:
    build:
      context: .
      dockerfile: Dockerfile
      target: prod
    image: php-mongodb-prod
    ports:
      - "8080:80"
    depends_on:
      - mongo
    env_file:
      - .env

  mongo:
    image: mongo
    ports:
      - "27017:27017"
    environment:
      MONGO_INITDB_ROOT_USERNAME: ${DB_USER}
      MONGO_INITDB_ROOT_PASSWORD: ${DB_PASSWORD}
    volumes:
      - mongo-data-prod:/data/db

  mongo-express:
    image: docker.io/library/mongo-express:1.0.2-20-alpine3.19
    ports:
      - "8081:8081"
    environment:
      ME_CONFIG_MONGODB_ADMINUSERNAME: ${DB_USER}
      ME_CONFIG_MONGODB_ADMINPASSWORD: ${DB_PASSWORD}
      ME_CONFIG_MONGODB_SERVER: mongo
      ME_CONFIG_BASICAUTH_USERNAME: ${DB_USER}
      ME_CONFIG_BASICAUTH_PASSWORD: ${DB_PASSWORD}
    depends_on:
      - mongo

volumes:
  mongo-data-prod:

composer.json

The composer.json file defines the PHP dependencies for the project. In this case, it includes the official MongoDB library for PHP, which allows the application to connect and interact with a MongoDB database.

{
  "require": {
    "mongodb/mongodb": "^2.0"
  }
}

db_connect.php

This file handles the connection to the MongoDB database. It loads credentials from environment variables and uses the official MongoDB PHP library to establish a connection. If the connection fails, an error message is displayed.

<?php
require 'vendor/autoload.php';

// Get credentials from environment (injected via Docker Compose env_file)
$mongoUser = getenv('DB_USER') ?: '';
$mongoPass = getenv('DB_PASSWORD') ?: '';

try {
    // Connect to MongoDB
    $client = new MongoDB\Client("mongodb://$mongoUser:$mongoPass@mongo:27017");

    // Select database
    $db = $client->myapp;

} catch (MongoDB\Driver\Exception\Exception $e) {
    echo "<h3>MongoDB Connection Error:</h3>";
    echo "<pre>" . $e->getMessage() . "</pre>";
    exit;
}

index.php

This is the main entry point of the web application. It connects to the MongoDB database using the db_connect.php file and queries the users collection. The retrieved user data is then displayed in a styled HTML table using Tailwind CSS. If the query fails or no users are found, a fallback message is shown.

<?php
    require_once __DIR__ . '/vendor/autoload.php';
    require_once './db_connect.php';

    $mongoUser = getenv('DB_USER');
    $mongoPass = getenv('DB_PASSWORD');

    try {
        $collection = $db->users;
        $users = $collection->find();
    } catch (MongoDB\Driver\Exception\Exception $e) {
        echo "<h3>Error querying MongoDB:</h3>";
        echo "<pre>" . $e->getMessage() . "</pre>";
        $users = [];
    }
?>
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://unpkg.com/@tailwindcss/browser@4"></script>
    <title>PHP with MongoDB</title>
</head>
<body class="max-w-4xl mx-auto p-12">
    <h1 class="text-4xl font-bold text-clifford mb-6">PHP with MongoDB</h1>

    <?php if (!empty($users)): ?>
        <table class="w-full text-sm text-left text-gray-900 shadow mt-12">
            <thead class="text-xs text-gray-700 uppercase bg-gray-50">
                <tr>
                    <th scope="col" class="px-6 py-3">
                        ID
                    </th>
                    <th scope="col" class="px-6 py-3">
                        Name
                    </th>
                    <th scope="col" class="px-6 py-3">
                        Email
                    </th>
                </tr>
            </thead>
            <tbody>
                <?php foreach ($users as $user): ?>
                    <tr class="bg-white border-b border-gray-200">
                        <td class="px-6 py-3"><?php echo htmlspecialchars($user['_id']); ?></td>
                        <td class="px-6 py-3"><?php echo htmlspecialchars($user['name']); ?></td>
                        <td class="px-6 py-3"><?php echo htmlspecialchars($user['email']); ?></td>
                    </tr>
                <?php endforeach; ?>
            </tbody>
        </table>
    <?php else: ?>
        <p class="text-gray-700 text-lg">No users found.</p>
    <?php endif; ?>
</body>
</html>

Running the App

Run in development mode:

This sets up the development environment with live code updates. First, generate the vendor/ folder using the PowerShell script, then start the application using the development Compose file.

./build-vendor.ps1
docker compose -f compose.dev.yaml up --build

Run in production mode:

This starts the application using the production image, where the source code and dependencies are already baked into the container. No volume mounts or extra setup is needed.

docker compose -f compose.prod.yaml up --build

Then open http://localhost:808 to view the PHP app, or http://localhost:8081 to access Mongo Express.

Conclusion

XAMPP is still a quick and simple way to start working with PHP, especially for beginners or in classroom settings. But when it comes to modern development workflows, Docker clearly offers more flexibility, scalability, and control.

With Docker, we can define our whole development environment in one place. That includes the PHP version, installed extensions, the database, and tools like phpMyAdmin or Mongo Express. This makes the setup easy to share, repeat, and more like what we would use in production. It's also simple to switch between different PHP versions. And we're not limited to just MySQL or MariaDB, with Docker Compose, we can easily add services like MongoDB, Redis, or anything else the project needs, all running in separate containers.

It takes a bit of time to understand Docker, but once you're comfortable, it becomes a powerful tool that fits perfectly into modern DevOps and software engineering practices.

If you're still using XAMPP today, it might be time to give Docker a try!

Full Code and Resources

All Dockerfiles, data, and benchmark scripts used in this post are available in my GitHub repository. You can check out the full code and experiment with the tests yourself:
GitHub Repository: php-docker-examples

1
Subscribe to my newsletter

Read articles from Simon directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Simon
Simon

Hi I am Simon, I am the Product owner of Python @Credit Suisse/UBS