6 min read
Building My Tech Portfolio: From Idea to Automated Deployment

“The best time to start was yesterday. The second best time is now.”

After weeks of thinking about it, I finally launched my own personal website — a portfolio that showcases my projects, experience, and tech blog.

In this post, I’ll share how I built it, the tools I used, what I learned along the way, and how I automated deployment using GitHub.


🧭 Why Build My Own Website?

I wanted a central place to share what I do — projects, experience, thoughts, and learning. It had to be minimal, fast, accessible, and most importantly: mine.

I also needed something easy to maintain and deploy without hassle every time I updated content.


🛠 Tools Used

  • Astro: a modern framework for building ultra-fast static sites. I liked its simplicity and how easily I could render dynamic content from .md files.
  • GitHub Pages: a free and simple hosting solution for static websites. Just push to the gh-pages branch, and it’s online.
  • GitHub Actions: to automate building and deployment every time I push to main.
  • Tailwind CSS: already included in the template, great for keeping the design clean and consistent. I was already familiar with it from previous study, which made it easier to customize styles effectively.

🏗 Site Development

I used Astro Nano as the base template. It comes with dark mode, accessibility, a clean design, and solid structure.

Here’s what I did:

  • Customized the theme, colors, and fonts
  • Replaced the default content with my own
  • Added my professional experience under /work/
  • Published my first project under /projects/
  • Prepared the blog structure for posts like this one

This process helped me better understand how Astro handles layouts, components, and content collections.


⚙️ Automating Deployment

This was one of the most valuable steps: automating the entire workflow.

With the help of ChatGPT, I built a GitHub Actions pipeline that:

  1. Triggers on push to main
  2. Installs dependencies and builds the project
  3. Copies index.html as 404.html to handle unmatched routes
  4. Adds a .nojekyll file to avoid GitHub Pages issues
  5. Pushes the contents of dist/ to the gh-pages branch

Now, I just write or code, commit, and the site updates itself — no manual steps.


🚀 Coming Soon

In the next blog post, I’ll dive into some Databricks topics — since that’s what I’m actively working with right now.

Thanks for reading, and see you in the next one!

title: “Stop Copy-Pasting dbutils: Introducing dbxparams” description: “How to eliminate widget duplication in Databricks notebooks with a clean, Pythonic approach” date: “2025-05-03” draft: false

If you work with Databricks notebooks, you’ve probably seen this pattern more times than you can count:

dbutils.widgets.text("source_path", "")
dbutils.widgets.text("output_table", "default.orders")
dbutils.widgets.text("full_refresh", "false")

source_path = dbutils.widgets.get("source_path")
output_table = dbutils.widgets.get("output_table")
full_refresh = dbutils.widgets.get("full_refresh")

It looks harmless… until you have dozens (or even hundreds) of notebooks that repeat the same widgets. Now imagine you need to add, remove, or rename one parameter. You end up touching every notebook manually. It’s repetitive, error-prone, and painful.

I got tired of it. That’s why I started building an open-source library: dbxparams.


1. The Idea

The core concept is simple:

  • Define your parameters once as a Python class.
  • Instantiate that class with dbutils.
  • dbxparams automatically creates the widgets and assigns their values to the class attributes.
  • Type checking ensures your parameters arrive in the correct format.

No more copy-paste. No more duplicated logic.


2. A First Example

from dbxparams import NotebookParams

class IngestParams(NotebookParams):
    source_path: str
    output_table: str = "default.orders_clean"
    full_refresh: bool = False

params = IngestParams(dbutils)

print("Source:", params.source_path)
print("Output table:", params.output_table)
print("Full refresh:", params.full_refresh)

What happens under the hood:

  1. When you call IngestParams(dbutils):

    • Creates the widgets (source_path, output_table, full_refresh).
    • Reads their values from the notebook UI.
    • Assigns them as attributes of your class.
  2. Type casting is automatic:

    • source_path → always a string.
    • full_refresh → parsed as a boolean (true/false/yes/no/1/0).
  3. If a required parameter is missing, dbxparams raises a MissingParameterError.


3. Type Checking and Validation

dbxparams supports type casting for:

  • str
  • int
  • float
  • bool

If the value doesn’t match, you’ll get a descriptive exception:

InvalidTypeError: Parameter 'max_rows' expected type int but received 'ten' (type str).

This makes debugging faster and your notebooks more robust. (Support for date and datetime will come in future releases.)


4. Example in a Databricks Job

dbxparams also works seamlessly with Jobs. Instead of defining widgets manually, just pass parameters in the Job config:

{
  "name": "IngestJobTest",
  "tasks": [
    {
      "task_key": "ingest_task",
      "notebook_task": {
        "notebook_path": "/Workspace/dbxparams_test/job_notebook",
        "base_parameters": {
          "source_path": "dbfs:/FileStore/raw/orders_2025.csv",
          "output_table": "analytics.cleaned_orders",
          "full_refresh": "true",
          "max_rows": "25000"
        }
      },
      "existing_cluster_id": "your-cluster-id"
    }
  ]
}

Inside the notebook:

params = IngestParams(dbutils)
print(params.source_path)       # dbfs:/FileStore/raw/orders_2025.csv
print(params.output_table)      # analytics.cleaned_orders
print(params.full_refresh)      # True
print(params.max_rows)          # 25000

5. Under the Hood

  • Inspects class annotations with typing.get_type_hints.

  • For each attribute:

    1. A widget is created (if not already present).
    2. The value is read from dbutils.widgets.
    3. If no value is found, it falls back to class defaults or raises MissingParameterError.
    4. The value is type-cast using a lightweight utility.

This design keeps notebooks clean, removes boilerplate, and ensures consistency across large ETL pipelines.


6. Roadmap

  • Add support for date and datetime.
  • Allow required parameters without type annotations.
  • Publish the library to PyPI in October.
  • Collect community feedback and contributions.

7. Conclusion

dbxparams is still a skeleton, but it already solves one of the most annoying problems in Databricks: parameter duplication.

By defining parameters once as a class, you get:

  • Cleaner notebooks
  • Centralized parameter definitions
  • Automatic widget creation
  • Built-in type checking

I’ll keep sharing progress and updates. If you’re interested in testing it or contributing, feel free to reach out.

Stay tuned — and no more copy-pasting dbutils.