I Built a Hackathon Platform Before I Knew What Docker Was
I am 17. I had just started my first semester of CSE. Dogfood 2026 was my first hackathon. And when I started, I did not know what Docker was. So naturally, I decided to build a self-hostable hackathon platform that
I am 17.
I had just started my first semester of CSE.
Dogfood 2026 was my first hackathon.
And when I started, I did not know what Docker was.
So naturally, I decided to build a self-hostable hackathon platform that runs on Docker.
This is the story of The Lone Wolf β my solo submission to Dogfood 2026.
The decision that shaped everything
The most important architectural decision I made was to keep the entire platform local and self-contained.
The application would run through Docker Compose, use SQLite for persistence, seed its own fixture data, and avoid depending on hosted databases, external APIs or cloud authentication.
At first, this simply felt like a constraint from the specification.
It eventually became the foundation of the system.
If the platform had to start locally and predictably, then the application, database, seed process, authentication, persistence and deployment setup all had to work together. There was no external service to quietly compensate for something I had implemented badly.
That changed the way I thought about the project.
I wasn't just building pages.
I was building a system that had to be reproducible from a clean start.
The Docker problem
This was my first practical encounter with Docker, and it cost me time because I initially treated Docker as something I could simply operate without understanding.
The command to start the application was easy.
Understanding what was actually happening was not.
I had to understand the relationship between my local environment, the container, dependencies, mounted data and persistence. In particular, resetting or recreating the environment was not equivalent to simply restarting the application.
That distinction became important later.
The useful lesson was not just βlearn Docker.β
It was:
When deployment is part of the architecture, you need to understand the environment in which your application runs, not just the command that starts it.
That became especially relevant because the platform had to be self-hostable rather than dependent on my particular machine.
The first requirement that looked simpler than it was
Submission deadline enforcement seemed straightforward.
A project should not be submittable after the deadline.
The obvious implementation would be to hide or disable the submit button.
But that isn't enforcement.
A user can bypass a UI restriction by calling the endpoint directly.
So the deadline had to be enforced by the backend itself.
That became a broader principle throughout the project:
The UI communicates a rule. The backend enforces it.
The same principle applied to role isolation. A participant should not merely be unable to see judge functionality; the backend should prevent access to it. A judge should not simply lack a button for another judge's scores; those permissions needed to be enforced server-side.
That was one of the first places where concepts such as authentication and authorization stopped being abstract terminology and became actual engineering decisions.
The fixture data was part of the engineering problem
The supplied fixture was deliberately more complicated than a few demonstration records.
It contained 40 teams, 30 judges, 41 projects, 8 tracks and 126 scores, along with awkward cases such as unfinished review batches, a judge who gave the same score across projects, and two projects called βDry Harbourβ belonging to the same team.
The Dry Harbour records were particularly useful.
Two projects with the same title and team initially looked like duplicate data.
They weren't invalid.
The important question was not βHow do I clean this up?β but βWhat does the data model actually permit?β
One team having multiple projects was valid, so both records needed to survive.
That influenced how I approached the fixture as a whole:
A system should handle unusual data according to its actual rules, rather than silently correcting anything that looks strange.
The fixture wasn't just sample content for the UI. It was effectively part of the test environment.
The scoring problem
Judging was another area where the implementation was more complicated than the requirement initially appeared.
The platform uses weighted rubric criteria, and the judging results are normalized across judges.
The difficult part was realizing that normalization is not simply a formula you can drop into an application and forget about.
A judge who uses almost the entire scoring range behaves differently from a judge who gives nearly everyone the same score.
The fixture actually contains the latter case.
That forced me to think about the assumptions behind the normalization rather than treating standard deviation and z-scores as purely mathematical operations.
The important lesson for me was:
A mathematically valid calculation can still produce an unsuitable application if the characteristics of the input data are ignored.
The implementation therefore had to account for the behaviour of the judging data, not just the nominal scoring formula.
Then my own implementation provided some additional testing
My rubric appeared three times.
The cause was in the seeding process: every initialization was inserting the criteria again because the database did not have the uniqueness behaviour I needed.
I changed the seeding logic so the event's rubric was reset and recreated deterministically.
Then the Save Score functionality broke because of a JavaScript scope issue.
These were ordinary bugs, but they changed how I approached the rest of the application.
Instead of asking only whether a feature worked in the successful path, I became much more interested in what happened when the state was repeated, missing, reset or unexpected.
That mindset was particularly useful for T3.
The part of the specification that looked simple
Community voting initially sounded almost trivial:
Users vote for projects.
Once implemented, it was clearly not trivial.
The requirement involved token-gated voting, randomized ballots, duplicate-vote prevention, comments, rate limiting, an audit trail and hidden results while voting was active.
The UI was not the difficult part.
The difficult part was making the rules hold consistently at the backend.
A token could not vote twice.
A voter needed access to the correct event's projects.
Comments needed abuse protection.
Results could not simply be exposed through another endpoint while voting was still open.
Randomization also needed to happen at the ballot level rather than being something cosmetic in the interface.
What looked like a single βvotingβ feature was actually a collection of integrity constraints.
That was probably the clearest example of why reading a specification and implementing a specification are two very different activities.
And then there was event_01
I reset my database to restore a clean fixture state for the final demonstration.
The reset also removed the voting window and voter token I needed for the community-voting demo.
So I had to recreate the voting window, generate a fresh token, verify the ballot and verify that the results remained hidden while voting was active.
It was frustrating at the time, but it exposed something useful about my architecture.
The voting window was persisted state.
The voter token was persisted state.
Resetting the database therefore legitimately removed both.
The system wasn't behaving mysteriously. I had simply changed the state I was asking it to operate on.
That distinction sounds obvious now.
It wasn't obvious to me when I was staring at the empty database.
What made this project mine
I started this hackathon with very little practical experience.
Python was still something I was learning. Git and GitHub were relatively new. APIs, authentication, authorization, SQL, Docker and the frontend were all things I was learning while building the project.
And I used ChatGPT heavily.
Very explicitly.
It was my tutor when I didn't understand a concept, my debugging partner when an error made no sense, and an architectural sounding board when I needed to reason through something I had never built before.
But the process was not simply βgenerate code and submit it.β
It was much more iterative:
Understand the requirement.
Figure out what I don't know.
Ask.
Implement.
Run it.
Find out what I misunderstood.
Fix it.
Test it again.
That distinction matters to me because the repository represents more than the final code. It represents the point at which I started understanding the systems I was building rather than merely recognizing the names of the technologies involved.
The Lone Wolf
By the end, I had a Dockerized, SQLite-backed platform implementing T1, T2 and T3, with participant, judge, organizer and admin workflows, deadline enforcement, judging, weighted rubrics, normalization, progress tracking, CSV export and community voting.
I also had a public GitHub repository and documentation.
But I don't think the most interesting part is the feature count.
It is the distance between where I started and where I ended.
I started the hackathon not knowing what Docker meant.
I ended it thinking about persistence, authorization boundaries, data integrity, normalization edge cases and what happens when a database is reset.
I'm still a beginner.
These are still baby steps.
But they are considerably more informed baby steps than the ones I started with.
So yes, I will farm sympathies for being a 17-year-old first-time hackathon participant who had to learn half the vocabulary while building the thing.
Not because being 17 is an engineering achievement.
Because it gives context to the learning curve.
I joined Dogfood as The Lone Wolf.
I came out with a working T1βT3 platform, several debugging scars, a much longer list of things I understand, and a considerably healthier respect for Docker.
And I can proudly say:
The first thing I ever tried was Dogfood.
Special mention: ChatGPT β Free Version.
Project
The complete project, including the source code, Docker setup, fixtures, tests and documentation, is available on GitHub:
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.