[{"content":"","date":"26/02/2026","externalUrl":null,"permalink":"/es/tags/github-actions/","section":"Tags","summary":"","title":"Github-Actions","type":"tags"},{"content":"","date":"26/02/2026","externalUrl":null,"permalink":"/es/categories/proyectos/","section":"Categories","summary":"","title":"Proyectos","type":"categories"},{"content":"","date":"26/02/2026","externalUrl":null,"permalink":"/es/categories/sistemas/","section":"Categories","summary":"","title":"Sistemas","type":"categories"},{"content":"","date":"26/02/2026","externalUrl":null,"permalink":"/es/categories/tutoriales/","section":"Categories","summary":"","title":"Tutoriales","type":"categories"},{"content":"","date":"26 February 2026","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"I have a server where I\u0026rsquo;ve deployed many services and applications that I use daily: web servers like Tutaim, Telegram bots, and game servers, among other things.\nBut what happens if one day the datacenter where my VPS is located burns down, everything gets wiped, or anything terrible happens that makes it unusable? I would lose absolutely all my services and would have to configure everything from scratch on a new server, which could easily take hours of work.\nThat\u0026rsquo;s why I thought about dockerizing all the services and applications I can. This way, if a catastrophe occurs, I can deploy them in minutes on any other server starting from scratch. To do this, in this guide I\u0026rsquo;ll explain the entire process with a real example: dockerizing a Telegram bot, pushing the image to GitHub, and deploying it.\n1. Install Docker: Engine or Desktop? # There are several ways to install Docker. We are interested in Docker Engine, the native engine we\u0026rsquo;ll use to create and run containers. However, there\u0026rsquo;s also Docker Desktop, which is a packaged GUI application that includes the engine, but packs a ton of extra stuff (and virtual machines) to make it \u0026ldquo;easy to use\u0026rdquo;.\nIn the following table, you can quickly see the key differences between both options:\nFeature Docker Engine Docker Desktop Architecture Background process (dockerd) + CLI. GUI App + Managed VM + Embedded Engine. Performance Native (bare-metal). Super light and fast. Heavy. Drags along a Virtual Machine and a graphical interface. Systems Native Linux (Requires manual VM on Windows/Mac). Windows, Mac, and Linux (runs everything via VM). Interface Pure terminal (CLI). User-friendly graphical interface. Cost 100% Open Source and free. Free for personal use. Paid for large companies. Ideal use The absolute standard for production servers. Only recommended for comfortable local development. Keeping these features in mind, choose one of the two. If you want maximum performance and you\u0026rsquo;re on Linux, stick with Docker Engine. If you want visual comfort on Windows or Mac, use Docker Desktop. Do not install both options at the same time on Linux, or you will face severe conflicts when running commands.\nI\u0026rsquo;m going to use Docker Engine on Linux, which is installed very easily by following these steps:\nDownload and install the official script: curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh Fix permissions (to be able to use Docker without typing sudo all the time): sudo usermod -aG docker $USER Important After running this command, log out or restart your PC for the permissions to apply.\n2. Prepare the application # The application I\u0026rsquo;m going to use to create the image is a simple Telegram bot that tells the time. I\u0026rsquo;ll create a folder with the 5 files I need:\n1. requirements.txt The file containing the required Python libraries.\npython-telegram-bot==20.8 python-dotenv==1.0.1 pytz==2024.1 2. .env The environment variables file. This file is a secret and must always remain local.\nTELEGRAM_TOKEN=123456:ABC-YourFakeTokenHere TIMEZONE=Europe/Madrid 3. .dockerignore Here we add all temporary cache files, IDE garbage, and most importantly, the .env file. Everything listed here will NOT be added to the public image.\n__pycache__/ *.pyc .env .git/ venv/ 4. main.py The code for my bot.\nimport os import logging from datetime import datetime import pytz from dotenv import load_dotenv from telegram import Update from telegram.ext import ApplicationBuilder, ContextTypes, CommandHandler # 1. Load environment variables (Hides your keys) load_dotenv() # 2. Configure Logging (To see errors in the console) logging.basicConfig( format=\u0026#39;%(asctime)s - %(name)s - %(levelname)s - %(message)s\u0026#39;, level=logging.INFO ) # 3. Bot Logic async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): await update.message.reply_text(\u0026#34;Hello! I\u0026#39;m TimeBot. Use /time to know what time it is.\u0026#34;) async def get_time(update: Update, context: ContextTypes.DEFAULT_TYPE): # We grab the timezone from the .env or use Madrid by default tz_name = os.getenv(\u0026#34;TIMEZONE\u0026#34;, \u0026#34;Europe/Madrid\u0026#34;) try: zone = pytz.timezone(tz_name) current_time = datetime.now(zone).strftime(\u0026#34;%H:%M:%S\u0026#34;) await update.message.reply_text(f\u0026#34;In {tz_name} the time is: {current_time}\u0026#34;) except Exception as e: logging.error(f\u0026#34;Timezone error: {e}\u0026#34;) await update.message.reply_text(\u0026#34;I had an issue calculating the time.\u0026#34;) # 4. Main Execution if __name__ == \u0026#39;__main__\u0026#39;: token = os.getenv(\u0026#34;TELEGRAM_TOKEN\u0026#34;) if not token: raise ValueError(\u0026#34;NO TOKEN PROVIDED! Check your .env file\u0026#34;) application = ApplicationBuilder().token(token).build() application.add_handler(CommandHandler(\u0026#39;start\u0026#39;, start)) application.add_handler(CommandHandler(\u0026#39;time\u0026#39;, get_time)) application.run_polling() 5. Dockerfile\nThe Dockerfile is the instruction file to create the image. If you\u0026rsquo;ve never written a Dockerfile, it might feel like you\u0026rsquo;re writing spells in an ancient language, but in reality, it\u0026rsquo;s the dumbest instruction manual in the world. It\u0026rsquo;s like handing an intern a sheet of paper telling them exactly how to set up a computer from scratch.\nFROM python:3.12-slim # Argument to detect architecture (amd64/arm64) ARG TARGETARCH WORKDIR /app # Install system dependencies if needed (e.g., ffmpeg, curl...) # RUN apt-get update \u0026amp;\u0026amp; apt-get install -y curl \u0026amp;\u0026amp; rm -rf /var/lib/apt/lists/* COPY requirements.txt . # Install Python libraries RUN pip install --no-cache-dir -r requirements.txt COPY . . # Startup command CMD [\u0026#34;python\u0026#34;, \u0026#34;main.py\u0026#34;] What exactly does this Dockerfile do? # Docker reads this file from top to bottom, executing each instruction and saving the result as a \u0026ldquo;layer\u0026rdquo; of the image. Let\u0026rsquo;s break down the most important instructions we\u0026rsquo;ve used:\nFROM python:3.12-slim (The base) This is always the first instruction. It tells Docker: \u0026ldquo;Download a lightweight Linux that already comes with Python 3.12 pre-installed.\u0026rdquo; This saves us from having to install the language manually.\nARG TARGETARCH (The architecture) This instruction is preparatory. It tells Docker to be ready to receive a magic external variable while it is building the image. It doesn\u0026rsquo;t make your image multi-architecture on its own (the buildx command we\u0026rsquo;ll see later handles that), but it is vital to include it if your application grows. Why? Because if tomorrow you need to install a Linux program that is named program-x64 on normal processors (Intel/AMD) and program-arm on a Raspberry Pi (ARM), you can use this variable inside the Dockerfile to say: \u0026ldquo;If TARGETARCH is amd64 download the first one, if it\u0026rsquo;s arm64 download the second one.\u0026rdquo; We aren\u0026rsquo;t actively using it in the RUN commands right now, but it\u0026rsquo;s a good practice to leave the Dockerfile prepared.\nWORKDIR /app (The working directory) It tells the container: \u0026ldquo;From now on, run all commands and copy all files inside the /app folder.\u0026rdquo; Obviously, you can store your files wherever you want inside your Docker image.\nRUN apt-get update... (Execute Linux commands) Using RUN followed by something is the equivalent of opening that virtual Linux terminal and typing that exact thing. Here you can install OS-level programs that your application might need under the hood (like ffmpeg if you\u0026rsquo;re processing audio/video, or curl).\nNote If you\u0026rsquo;re going to execute multiple system commands, always chain them with \u0026amp;\u0026amp; in a single RUN. If you put 10 separate RUN commands, Docker will create 10 useless \u0026ldquo;layers\u0026rdquo; and your image will be much heavier. For a better optional understanding of Docker layers, you can review its official explanation.\nCOPY requirements.txt . (Throw your files in the pot) Copies files from your computer into the image. Notice that we first copy only the requirements file and then run pip install in the next RUN. We do this to take advantage of Docker\u0026rsquo;s caching system: if you modify your code in main.py but don\u0026rsquo;t touch the libraries, Docker will skip the pip installation and build the image much faster. COPY . . (The final dump) Copies EVERYTHING inside your project folder (your source code, folders, etc.) into the image. Anything you don\u0026rsquo;t want to include (like the .env with your real keys or the temporary __pycache__ files), must be listed in the .dockerignore file. CMD [\u0026quot;python\u0026quot;, \u0026quot;main.py\u0026quot;] (The power button) This is the final order. It tells the container what it has to do when someone turns it on on the server. Be careful with this: if the main.py script crashes or finishes executing, the entire container will shut down instantly. Docker only keeps the container alive as long as the CMD process keeps running in the foreground. Additionally, this file is prepared with the TARGETARCH variable in case we want to install different dependencies in the future depending on whether the server uses Intel/AMD processors (amd64) or ARM processors like a Raspberry Pi (arm64).\n3. Upload the image to GitHub (GHCR) # To be able to download our application from any server in the world, we are going to host the Docker image in the GitHub Container Registry.\nTo do this, you need a Token. Generate a new one and make sure to check the write:packages box. Copy that token.\nNow, open your terminal and log in to Github:\ndocker login ghcr.io -u YourGitHubUsername Paste the generated token when prompted.\n4. Native and multi-architecture build # Multi-architecture # Normally, if you build the image on your PC (amd64), it won\u0026rsquo;t work on ARM machines (arm64) like a Raspberry Pi. To solve this and build both versions at once, we\u0026rsquo;ll use Docker Buildx.\nRun these three commands in your local terminal, inside the project folder:\n# 1. Install emulators so your PC can compile ARM (only the first time) docker run --privileged --rm tonistiigi/binfmt --install all # 2. Create an advanced builder (only the first time) docker buildx create --use # 3. Compile both versions at once and automatically push them to GitHub docker buildx build --platform linux/amd64,linux/arm64 -t ghcr.io/YourGitHubUsername/timebot:latest --push . Once it finishes, the image will be available in the \u0026ldquo;Packages\u0026rdquo; tab of your GitHub profile:\nNative # If you\u0026rsquo;re only interested in building the image for your current system\u0026rsquo;s architecture, simply run these commands and you\u0026rsquo;re done:\n# 1. Build the image docker build -t ghcr.io/YourGitHubUsername/timebot:latest . # 2. Push it to the GitHub registry docker push ghcr.io/YourGitHubUsername/timebot:latest Important ghcr.io/YourGitHubUsername/timebot:latest is not just the image name, it\u0026rsquo;s the entire shipping address, as if it were a postal package label. We\u0026rsquo;re telling it to push the image to the GitHub Container Registry under the /YourGitHubUsername/timebot path.\n:latest is the specific version of the image. In a professional environment, a numerical tag is usually used, but this works fine for us.\n5. Deployment on the server (the orchestrator) # We finally arrive at the production server. Access a terminal on your server or VPS.\nAt this point, you could turn on your container using a ridiculously long docker run command in the terminal, but that\u0026rsquo;s a bad practice. If the server restarts, the container won\u0026rsquo;t start by itself and you\u0026rsquo;ll lose the configuration.\nTo fix this we use Docker Compose, which is a \u0026ldquo;local orchestrator\u0026rdquo;. It allows us to write a configuration file (docker-compose.yml) where we define how we want our container to behave, if it needs to restart automatically, what ports it uses, and where it reads passwords from.\nSince our bot\u0026rsquo;s code is already inside the image hosted on GitHub, you don\u0026rsquo;t need to upload or copy your Python files to the VPS. You only need to tell Docker Compose to download the image and turn it on.\nCreate a new folder on your server, enter it, and create your credentials file. In our case, it does need one, since it\u0026rsquo;s a file that doesn\u0026rsquo;t come with the image; it must be created locally for security.\nmkdir timebot \u0026amp;\u0026amp; cd timebot nano .env Next, create the orchestrator. The name must be docker-compose.yml or compose.yml. In recent versions, Docker\u0026rsquo;s documentation recommends using compose.yml, although docker-compose.yml is still completely valid.\nnano docker-compose.yml And paste the following configuration:\nservices: timebot: image: ghcr.io/YourGitHubUsername/timebot:latest container_name: timebot_prod restart: always env_file: - .env What does each line of the docker-compose.yml mean? # services:: This is where the list of applications we are going to boot up begins. In our case, just one (our bot). timebot:: This is the internal name we give to the service. If we were to add a PostgreSQL database in the future, we would put it underneath with the name db:. image:: Tells Docker exactly where it has to download the package from. By appending :latest, we ensure it always points to the latest version we\u0026rsquo;ve uploaded to GitHub. container_name:: This is the \u0026ldquo;pretty\u0026rdquo; name you\u0026rsquo;ll see when you list your server\u0026rsquo;s processes with docker ps. restart: always: The lifeline. It tells Docker that if the Python script crashes due to an error, or if the entire VPS server restarts due to a power outage, Docker must turn the bot back on automatically. env_file:: Securely injects the passwords and configurations we saved in our server\u0026rsquo;s .env file, so the Python code can read the TELEGRAM_TOKEN without it being public on GitHub. To see all the options besides the ones explained here that the Docker Compose file offers, I recommend checking the official Docker documentation.\nOnce the file is saved, start the bot in the background by running:\ndocker compose up -d (The -d or \u0026ldquo;detached\u0026rdquo; parameter is used so the bot stays running in the background and gives you back control of the terminal).\nWe execute it and wait for it to download the image and boot it up:\nubuntu@myvps:~/dockers/timebot$ docker compose up -d [+] up 11/11 ✔ Image ghcr.io/mr-umar/timebot:latest Pulled 2.4s ✔ Network timebot_default Created 0.5s ✔ Container timebot_prod Started 0.3s ubuntu@myvps:~/dockers/timebot$ To verify that our container is working, we can check it with docker ps:\nubuntu@myvps:~/dockers/timebot$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 4ba9a9dd53df ghcr.io/mr-umar/timebot:latest \u0026#34;python main.py\u0026#34; About a minute ago Up About a minute timebot_prod As we can see, the bot is now running in the cloud securely, isolated, and ready to survive reboots.\n6. Updating in the future (The DevOps cycle) # Have you modified the code on your computer and want to update the application on the VPS? This is the process you should follow:\nOn your PC: Run the push command again. docker buildx build --platform linux/amd64,linux/arm64 -t ghcr.io/YourGitHubUsername/timebot:latest --push . or\n# 1. Build the image docker build -t ghcr.io/YourGitHubUsername/timebot:latest . # 2. Push it to the GitHub registry docker push ghcr.io/YourGitHubUsername/timebot:latest On your VPS: Download the update and restart the container. docker compose pull docker compose up -d With these two simple steps, Docker detects the new version, shuts down the old container, and boots up the new one in a matter of seconds. You now have a complete DevOps system that will save your life the next time your server decides to catch fire.\nConclusion # This Telegram bot is just the excuse: the workflow is the same for almost any app or service you want to make \u0026ldquo;fireproof\u0026rdquo; (Dockerfile → build → push to a registry → deploy with Compose on the server → update with pull + up). The magic isn\u0026rsquo;t the example; it\u0026rsquo;s that you separate code (inside the image) from config/secrets (outside, in .env/variables), and this way you can recreate everything in minutes on any clean machine without copying projects by hand.\nFrom here on out, your job is to repeat the pattern: if the app uses ports, declare them in Compose; if it needs persistence, add volumes; if it depends on other services (DB, Redis), add them as more services and you\u0026rsquo;re set. To avoid messing up, when you want to do something \u0026ldquo;weird\u0026rdquo; (healthchecks, networks, volumes, resource limits, depends_on, etc.), rely first on the official Docker Compose documentation, because that\u0026rsquo;s where all the options are with their real and updated behavior. And yes: you can use an LLM to generate the skeleton of the Dockerfile/compose.yml or to adapt the deployment to your case, but then don\u0026rsquo;t be an NPC: test it locally, and version/tag your images if you don\u0026rsquo;t want surprises.\n","date":"26 February 2026","externalUrl":null,"permalink":"/posts/dockerize-apps-vps-github-container-registry-multi-arch/","section":"Posts","summary":"","title":"Complete guide to Dockerize apps and services","type":"posts"},{"content":"","date":"26 February 2026","externalUrl":null,"permalink":"/tags/containers/","section":"Tags","summary":"","title":"Containers","type":"tags"},{"content":"","date":"26 February 2026","externalUrl":null,"permalink":"/tags/devops/","section":"Tags","summary":"","title":"Devops","type":"tags"},{"content":"","date":"26 February 2026","externalUrl":null,"permalink":"/categories/devops/","section":"Categories","summary":"","title":"DevOps","type":"categories"},{"content":"","date":"26 February 2026","externalUrl":null,"permalink":"/tags/docker/","section":"Tags","summary":"","title":"Docker","type":"tags"},{"content":"","date":"26 February 2026","externalUrl":null,"permalink":"/tags/ghcr/","section":"Tags","summary":"","title":"Ghcr","type":"tags"},{"content":"Hi there 👋. Here you\u0026rsquo;ll find documentation I write as future reference for myself (in case I forget something later), along with interesting findings and things I\u0026rsquo;m currently learning.\nI hope you find it useful! You can search for specific topics using the magnifying glass icon above 🔍 or just browse my recent posts below.\n","date":"26 February 2026","externalUrl":null,"permalink":"/","section":"Home","summary":"","title":"Home","type":"page"},{"content":"","date":"26 February 2026","externalUrl":null,"permalink":"/categories/linux/","section":"Categories","summary":"","title":"Linux","type":"categories"},{"content":"","date":"26 February 2026","externalUrl":null,"permalink":"/posts/","section":"Posts","summary":"","title":"Posts","type":"posts"},{"content":"","date":"26 February 2026","externalUrl":null,"permalink":"/categories/projects/","section":"Categories","summary":"","title":"Projects","type":"categories"},{"content":"","date":"26 February 2026","externalUrl":null,"permalink":"/tags/python/","section":"Tags","summary":"","title":"Python","type":"tags"},{"content":"","date":"26 February 2026","externalUrl":null,"permalink":"/tags/self-hosting/","section":"Tags","summary":"","title":"Self-Hosting","type":"tags"},{"content":"","date":"26 February 2026","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"","date":"26 February 2026","externalUrl":null,"permalink":"/tags/telegram-bot/","section":"Tags","summary":"","title":"Telegram-Bot","type":"tags"},{"content":"","date":"26 February 2026","externalUrl":null,"permalink":"/categories/tutorials/","section":"Categories","summary":"","title":"Tutorials","type":"categories"},{"content":"","date":"26 February 2026","externalUrl":null,"permalink":"/tags/vps/","section":"Tags","summary":"","title":"Vps","type":"tags"},{"content":"","date":"19 February 2026","externalUrl":null,"permalink":"/tags/android/","section":"Tags","summary":"","title":"Android","type":"tags"},{"content":"","date":"19/02/2026","externalUrl":null,"permalink":"/es/tags/apuntes/","section":"Tags","summary":"","title":"Apuntes","type":"tags"},{"content":"A few months ago, I acquired the Xiaomi Pad 7 along with its original stylus, the Focus Pen, at a very good price. My goal was to use it for university notes, so over these last few months, I have tested dozens of handwriting apps, and I believe I have finally found the best ones. In this post, I will talk about some options I tested and which ones are the best in my opinion.\nMi Canvas: The native disappointment # Mi Canvas is the default application pre-installed on the Xiaomi Pad 7 and other tablets from the brand. It is decent for quick doodles, but it is not designed for serious university note-taking.\nThe first major issue is that the strokes are NOT vector-based. What does this mean? It means that when you share or export your notes, they will pixelate when zoomed in. Look at the image below: the text on the left (Mi Canvas) is clearly pixelated; the one on the right (another app with vector strokes) remains infinitely sharp. For me, this rules it out immediately.\nOther serious flaws:\nNo navigation: With long notes, you cannot easily jump between pages. You have to scroll endlessly to find previous information. No organization: All notes are in a single folder. Impossible to organize by subjects, topics, or semesters. Limited cloud: It only syncs with Xiaomi Cloud. No Google Drive, Dropbox, or anything useful. It serves for a quick sketch or screenshot, but for uni notes, it is a joke.\nScore 4/10\n✅ The good (Mi Canvas) ❌ The bad (Mi Canvas) Native integration: Works perfectly with the pen buttons (to change tools and colors) without configuring anything. Lack of functions: No layers, No organization options, nor many customization options. Minimal latency: Being a system app, the stroke is almost instantaneous. Poor interface: Few brushes, no advanced page customization options. Free: Comes installed, zero cost. No decent export: Forget about getting a high-quality vector PDF or easily viewing notes on PC. Simplicity: \u0026ldquo;Plug and play,\u0026rdquo; ideal for quick notes or messy doodles. Samsung Notes: The exclusive gem # If you have a Samsung tablet, look no further: this is your best option, period. It is the native application par excellence and, unfortunately, it is exclusive to the Samsung ecosystem. Although modified APKs exist for other brands, they usually have sync issues or capped features, so if you have a tablet from another brand, forget about using it 100%.\nTechnically, it is everything Mi Canvas should be and is not. Samsung Notes plays in another league: impeccable vector writing engine, perfect cloud sync (Microsoft OneNote/PC), drawing layers, audio recording synced with what you write (brutal for lectures), and PDF management that is a delight.\nIf you have a Samsung tablet, it is probably the best option.\nScore 8.5/10\n✅ The good (Samsung Notes) ❌ The bad (Samsung Notes) Vector engine: Perfect stroke and infinite scaling without pixelation. Exclusivity: Only works well on Samsung Galaxy devices. Audio sync: Records the class while you take notes; during playback, it highlights what you were writing at that moment. Lock-in: If you switch brands (to Xiaomi/iPad), exporting your editable notes is a pain. PC ecosystem: Has a native app for Windows (although it only installs easily on Samsung Galaxy Book laptops; others require tricks). Linux sync: No native client; you depend on OneNote web or backups. Free and complete: Comes pre-installed with Pro features that others charge for. Squid Notes: The efficient veteran # Squid is one of the pioneer apps on Android for handwritten notes and, surprisingly, it is still putting up a fight. Although its free version is very limited for serious use (you will miss basic tools), its subscription model is ridiculously cheap: €1 per month or €10 per year. A symbolic price for a professional tool.\nIts greatest virtue is technical: it uses an OpenGL-based vector rendering engine that achieves extremely low latency even on old devices. This means the \u0026ldquo;ink\u0026rdquo; flows from the pen tip with no perceptible delay, which is critical for writing fast. Additionally, it generates very light vector PDF files, perfect for sharing or printing without losing quality.\nHowever, its age shows. Despite being on the market for over a decade, it still lacks a native PC client or web version, which is unforgivable in 2026. The only solution is to enable \u0026ldquo;Automatic PDF Export\u0026rdquo; to the cloud (Google Drive/Dropbox/Box) to have a readable copy on your computer.\nThey recently gave the interface a facelift with a more modern design, which was much needed, although the pure editing experience (selecting, moving, changing color) still feels a step behind modern apps.\nIt is the ideal option if you want speed, stability, and files that do not take up gigabytes. Do not expect artistic flourishes; it is a brute work tool.\nScore 7/10\n✅ The good (Squid) ❌ The bad (Squid) Performance: Minimal latency and extreme fluidity on any tablet. No PC/Web client: Forced to use \u0026ldquo;hacks\u0026rdquo; (auto PDF export) to view notes on Linux/Windows. Light files: Generates vector PDFs that take up very little space. Interface: Although improved, the editing UX feels \u0026ldquo;old.\u0026rdquo; Price: €10/year is unbeatable for a pro tool. Reliability: It is a rock, does not crash and syncs correctly. Saber Notes: The open source alternative # Saber is the application I have used for the longest time and, for me, the only real open-source competition against giants like Samsung Notes. In fact, its creator designed it explicitly to fill that gap in the free Android ecosystem.\nBeing open source, you have total control: you can audit the code, see how it works inside, and even make your own fork to add specific functions, something I did myself to adapt it to my needs.\nIts strong point is real cross-platform compatibility: it has native clients for Android, Linux (Flatpak), Windows, and even iOS. If you use Linux on the desktop, it is a joy to have your notes synced without juggling.\nHowever, it is not perfect. Cloud synchronization relies exclusively on Nextcloud/WebDAV, which is great for privacy (you can self-host your server), but requires technical configuration if you do not have hosting handy.\nThe big problem with Saber is performance and file weight. By saving highly detailed vector strokes without much compression, a note of a few pages can take up hundreds of megabytes. on mid-range or low-end tablets, the app feels heavy and scrolling can stutter.\nIt has compatibility with a wide variety of pencils, although unfortunately not for my Focus Pen. ;(\nIt is the mandatory option to try before paying anything. If you have a powerful tablet and like open source, it could be your definitive app. It works in any tablet or pc with any operative system\nScore 9/10\n✅ The good (Saber) ❌ The bad (Saber) Open source: Free code, no trackers, and modifiable. Heavy files: Simple notes can take up \u0026gt;100 MB due to lack of optimization. Cross-platform: Native client for Linux, Windows, Android, and iOS that works the same everywhere. Performance: Demands powerful hardware to run smoothly with many pages. Total privacy: Synchronization via Nextcloud/WebDAV (your data is yours). Limited sync: Does not support native GDrive/Dropbox. Free: No subscriptions, no ads, no in-app purchases. Occasional bugs: Sometimes it has silly glitches. Notein: The definitive revelation # For me, Notein is the definitive application on Android today. It is the direct answer to iOS\u0026rsquo;s GoodNotes, developed by ex-Huawei and Baidu engineers who know very well what they are doing.\nTechnically it is a beast: it has absolutely every function a student could dream of. Perfect vector strokes, instant geometric shape recognition, AI OCR (only paid version), multiple layers, and varios option of sheet/template customization.\nIt is the only application of all those I have tested that implements a superior tab system (like a web browser), allowing you to have multiple notes and PDFs open simultaneously to consult or copy content without wasting time switching windows.\nWhat finally made me fall in love was its hardware integration: it is the only third-party app (besides the native Mi Canvas) that correctly detects and uses the Xiaomi Focus Pen buttons to switch tools or erase without touching the screen.\nIts price is ridiculous for what it offers: about €7 per year or a one-time payment of €14 for life. I paid for the lifetime license without hesitation.\nThe only important point is it\u0026rsquo;s availability; it is exclusive to Android. It has no native PC client (Windows/Linux) nor web version. However, as it is under constant development, I hope it will have a PC client in the future.\nIf you do not have a Samsung tablet, this is your app. It combines the speed of Squid with the functions of Samsung Notes. It is the perfect substitute for Saber if you seek stability and do not mind paying €14.\nScore 9/10\n✅ The good (Notein) ❌ The bad (Notein) Graphic engine: Zero latency and ultra-fluid vector stroke. Android only: No native PC/Web client (you depend on PDF backup). Xiaomi hardware: Native support for Focus Pen buttons (rare in third-party apps). Learning curve: So many options can overwhelm at first. Pro features: Layers, OCR, audio sync, real infinite zoom. Paid: Although cheap, it has no free \u0026ldquo;full\u0026rdquo; version like Saber. Price: Affordable one-time payment (€14) vs. eternal subscriptions. Privacy: Servers in China/Singapore (although you use GDrive for backup). Conclusion: Which is the best app for you? # After testing them all intensively in my day-to-day life as an engineering student, the answer depends on your hardware and your workflow.\nIf you have a Samsung tablet, do not complicate things: Samsung Notes is unbeatable in its ecosystem and free.\nIf you want pure speed on any old tablet, Squid remains the king of performance.\nBut if you have a non-Samsung tablet and want the most productive experience, Saber Notes and Notein are the absolute winners. As a personal recommendation, try both. Even if you are a Samsung user too.\nApp Ideal for Price Linux/Windows Final score Notein Android users (Engineering/Serious study) ~€14 (Lifetime) Android only 😃 Score 9/10 (Highly recommended) Saber Any student Free (Open Source) Native client 😃 Score 9/10 (Highly recommended) Samsung Notes Galaxy Tab owners (Exclusive) Free (Included) Windows/Web only 😊 Score 8.5/10 (Recommended for Samsung users) Squid Old/slow tablets (speed) ~€10/year PDF with backups only 🙂 Score 7/10 (Recommended) Mi Canvas Quick notes/sketches (casual use) Free (Included) No 😢 Score 4/10 (Not recommended) ","date":"19 February 2026","externalUrl":null,"permalink":"/posts/best-tablet-note-taking-apps-for-engineering-and-university-students/","section":"Posts","summary":"","title":"Best tablet note-taking apps for university students","type":"posts"},{"content":"","date":"19 February 2026","externalUrl":null,"permalink":"/tags/engineering/","section":"Tags","summary":"","title":"Engineering","type":"tags"},{"content":"","date":"19/02/2026","externalUrl":null,"permalink":"/es/tags/estudio/","section":"Tags","summary":"","title":"Estudio","type":"tags"},{"content":"","date":"19/02/2026","externalUrl":null,"permalink":"/es/tags/ingenieria/","section":"Tags","summary":"","title":"Ingenieria","type":"tags"},{"content":"","date":"19/02/2026","externalUrl":null,"permalink":"/es/tags/lapiz/","section":"Tags","summary":"","title":"Lapiz","type":"tags"},{"content":"","date":"19/02/2026","externalUrl":null,"permalink":"/es/tags/matematicas/","section":"Tags","summary":"","title":"Matematicas","type":"tags"},{"content":"","date":"19 February 2026","externalUrl":null,"permalink":"/tags/math/","section":"Tags","summary":"","title":"Math","type":"tags"},{"content":"","date":"19/02/2026","externalUrl":null,"permalink":"/es/tags/notas/","section":"Tags","summary":"","title":"Notas","type":"tags"},{"content":"","date":"19 February 2026","externalUrl":null,"permalink":"/tags/note-taking/","section":"Tags","summary":"","title":"Note-Taking","type":"tags"},{"content":"","date":"19 February 2026","externalUrl":null,"permalink":"/tags/notes/","section":"Tags","summary":"","title":"Notes","type":"tags"},{"content":"","date":"19/02/2026","externalUrl":null,"permalink":"/es/categories/productividad/","section":"Categories","summary":"","title":"Productividad","type":"categories"},{"content":"","date":"19 February 2026","externalUrl":null,"permalink":"/categories/productivity/","section":"Categories","summary":"","title":"Productivity","type":"categories"},{"content":"","date":"19/02/2026","externalUrl":null,"permalink":"/es/categories/rese%C3%B1as/","section":"Categories","summary":"","title":"Reseñas","type":"categories"},{"content":"","date":"19 February 2026","externalUrl":null,"permalink":"/categories/reviews/","section":"Categories","summary":"","title":"Reviews","type":"categories"},{"content":"","date":"19 February 2026","externalUrl":null,"permalink":"/tags/study/","section":"Tags","summary":"","title":"Study","type":"tags"},{"content":"","date":"19 February 2026","externalUrl":null,"permalink":"/tags/stylus/","section":"Tags","summary":"","title":"Stylus","type":"tags"},{"content":"","date":"19 February 2026","externalUrl":null,"permalink":"/tags/tablet/","section":"Tags","summary":"","title":"Tablet","type":"tags"},{"content":"","date":"19 February 2026","externalUrl":null,"permalink":"/categories/technology/","section":"Categories","summary":"","title":"Technology","type":"categories"},{"content":"","date":"19/02/2026","externalUrl":null,"permalink":"/es/categories/tecnologia/","section":"Categories","summary":"","title":"Tecnologia","type":"categories"},{"content":"","date":"19/02/2026","externalUrl":null,"permalink":"/es/tags/universidad/","section":"Tags","summary":"","title":"Universidad","type":"tags"},{"content":"","date":"19 February 2026","externalUrl":null,"permalink":"/tags/university/","section":"Tags","summary":"","title":"University","type":"tags"},{"content":"","date":"15/02/2026","externalUrl":null,"permalink":"/es/categories/gu%C3%ADa/","section":"Categories","summary":"","title":"Guía","type":"categories"},{"content":"","date":"15 February 2026","externalUrl":null,"permalink":"/categories/guide/","section":"Categories","summary":"","title":"Guide","type":"categories"},{"content":"","date":"15 February 2026","externalUrl":null,"permalink":"/tags/hardware/","section":"Tags","summary":"","title":"Hardware","type":"tags"},{"content":"If you\u0026rsquo;ve made it this far, you\u0026rsquo;ve probably realized that Nvidia and Linux in general don\u0026rsquo;t get along very well\u0026hellip; So in this quick guide, I\u0026rsquo;m going to explain how to activate your Nvidia GPU on Ubuntu-based Linux distributions like Linux Mint, KDE Neon, Pop!_OS, and many others.\nFirst things first, let\u0026rsquo;s verify just in case that our distro is actually based on Ubuntu; obviously, if you use Ubuntu, you don\u0026rsquo;t need to do this.\ncat /etc/os-release | grep ubuntu If this command shows an output, like the one below, then you can proceed without fear:\numar@crappycomputer:~$ cat /etc/os-release | grep ubuntu ID_LIKE=\"ubuntu debian\" This procedure assumes that the NVIDIA drivers are not yet installed or that you are starting from a clean configuration.\nStep 1: Install the NVIDIA Driver # Open a terminal (Ctrl+Alt+T). Search for the recommended drivers for your hardware with the following command: ubuntu-drivers devices This produces the following output:\numar@crappycomputer:~$ ubuntu-drivers devices == /sys/devices/pci0000:00/0000:00:06.0/0000:01:00.0 == modalias : pci:v000010DEd000025A2sv00001043sd00001113bc03sc02i00 vendor : NVIDIA Corporation model : GA107M [GeForce RTX 3050 Mobile] (...) driver : nvidia-driver-580-open - distro non-free driver : nvidia-driver-590-server - distro non-free driver : nvidia-driver-590-open - distro non-free recommended driver : nvidia-driver-535 - distro non-free driver : nvidia-driver-470-server - distro non-free driver : xserver-xorg-video-nouveau - distro free builtin Install the driver marked as recommended. In my case, it\u0026rsquo;s nvidia-driver-590-open. Run the following command to install it: sudo apt install nvidia-driver-590-open Once the installation finishes, restart your computer. Step 2: Switch to the X11 Graphical Session # This is the most important part to guarantee compatibility.\nOn the login screen (where you enter your password), look for an icon or dropdown menu in one of the corners. Select the Plasma (X11) option instead of \u0026ldquo;Plasma (Wayland)\u0026rdquo;. Log in as you normally would. Step 3: Configure the System to Use Only the NVIDIA GPU # Open a terminal. Run the following command to set the NVIDIA performance profile: sudo prime-select nvidia Restart the computer for the changes to apply correctly. Step 4: Verify Everything Worked # After restarting, open a terminal and run the NVIDIA monitoring command:\nnvidia-smi If a table appears with your graphics card information and shows the /usr/lib/xorg/Xorg process, it means the NVIDIA GPU is active and rendering the entire desktop.\nImportant It is possible that after an update, the NVIDIA GPU might not remain set as the primary one. In that case, simply repeating the steps starting from Step 3 is sufficient.\nMission Center # A good graphical application to monitor your Linux system is Mission Center, where you can view usage, memory, and video encoding/decoding for each GPU. If you see that your Nvidia card has zero usage, something went wrong, or the NVIDIA GPU is no longer the primary one after an update; in that case, simply repeating the steps starting from Step 3 is sufficient.\nI hope this quick guide is useful to you.\n","date":"15 February 2026","externalUrl":null,"permalink":"/posts/activate-nvidia-drivers-graphic-cards-on-ubuntu-distributions/","section":"Posts","summary":"","title":"How to enable Nvidia GPU on Ubuntu-based distributions","type":"posts"},{"content":"","date":"15 February 2026","externalUrl":null,"permalink":"/tags/linux/","section":"Tags","summary":"","title":"Linux","type":"tags"},{"content":"","date":"15 February 2026","externalUrl":null,"permalink":"/tags/nvidia/","section":"Tags","summary":"","title":"Nvidia","type":"tags"},{"content":"","date":"15 February 2026","externalUrl":null,"permalink":"/tags/os/","section":"Tags","summary":"","title":"OS","type":"tags"},{"content":"","date":"15/02/2026","externalUrl":null,"permalink":"/es/tags/so/","section":"Tags","summary":"","title":"SO","type":"tags"},{"content":"","date":"15 February 2026","externalUrl":null,"permalink":"/tags/ubuntu/","section":"Tags","summary":"","title":"Ubuntu","type":"tags"},{"content":"","date":"14 February 2026","externalUrl":null,"permalink":"/tags/blowfish/","section":"Tags","summary":"","title":"Blowfish","type":"tags"},{"content":" What is Hugo? # If you\u0026rsquo;re looking for something to manage a blog, documentation, or just a website where you can publish your stuff, and you\u0026rsquo;ve realized that WordPress is slow, insecure, and you\u0026rsquo;re sick of updating plugins every two days just to stop the Russians or Chinese from breaking in\u0026hellip;\nThe solution is Hugo. It\u0026rsquo;s a static site generator built in Go. It compiles your entire website in milliseconds. But before you start, you need to know that the pages you generate will be written in Markdown, or, if you\u0026rsquo;re too scared to use the Linux terminal even a little bit, then quit now and go use Wix.\nAside from Hugo, I\u0026rsquo;m going to show you how to install the Blowfish theme, which is the one I currently use.\nRequirements # I\u0026rsquo;m going to teach you how to install Hugo on Linux and host it on GitHub Pages, so you need:\nA GitHub account. Since we\u0026rsquo;re using GitHub, you obviously need to have Git installed on your machine. A code editor. You can use Gedit, Visual Studio, Sublime Text, or even nano or vim if that works for you. Use whatever you know. I recommend Visual Studio Code. Installation # With Snap Store # There are several ways to install Hugo; the first and easiest one, if you have Snap, is to paste the following command into your terminal:\nsudo snap install hugo --channel=extended We need the Extended version for Sass/SCSS support.\nThe General Way for Linux # If you don\u0026rsquo;t have Snap, or you just don\u0026rsquo;t feel like using it, you can download the .deb package from the official Hugo repository.\nGo to GitHub Releases. Look for the .deb file that says hugo_extended_X.XX.X_linux-amd64.deb. It is crucial that it includes the word extended.\nNote If you are on a machine with ARM architecture (if you type arch in your terminal, it will tell you your machine\u0026rsquo;s architecture), you need to look for hugo_extended_X.XX.X_linux-arm64.deb.\nOnce you have the .deb, download it manually or use wget \u0026lt;download link\u0026gt;.\nTo install it, navigate to the directory where you have the .deb and execute:\nsudo dpkg -i hugo_extended_*.deb Now, to check that everything went well, close your current terminal, open a new one, and run:\nhugo version You should see something like:\numar@crappycomputer:~$ hugo version hugo v0.155.3-8a85c04d295+extended linux/amd64 (...) With this, you now have Hugo on your machine.\nYour First Website # Now that you have Hugo, let\u0026rsquo;s create your website, blog, portfolio, or whatever the hell it\u0026rsquo;s for. The key command here is the following, where my-blog will be the name of the folder Hugo creates for your site.\nhugo new site my-blog Note You can create it wherever you want, but I recommend keeping it organized so you know where it is when you want to edit it later. This will be your local repository where you\u0026rsquo;ll make changes before uploading them.\nNow that you have it, go inside and initialize your repo:\ncd my-blog git init Create and Connect the GitHub Repository # Before continuing, you need to create a repository on GitHub and connect it to your local repo.\nGo to GitHub and create a new repository.\nImportant If you want your site to be at your-username.github.io, the repo name must be exactly your-username.github.io. If you name it something else like my-blog, the URL will be your-username.github.io/my-blog.\nWhen you create the repo, DO NOT check the options for \u0026ldquo;Add a README file\u0026rdquo;, \u0026ldquo;.gitignore\u0026rdquo;, or \u0026ldquo;license\u0026rdquo;. Leave it empty because you already have the local content. Also, if you don\u0026rsquo;t have GitHub Pro, the repo must be public.\nOnce the repo is created, copy the URL GitHub gives you (something like https://github.com/your-username/repo-name.git) and run:\ngit remote add origin https://github.com/your-username/repo-name.git Installing the Blowfish Theme # Now we\u0026rsquo;re going to install the Blowfish theme; it\u0026rsquo;s a theme with a lot of possibilities, and I think it\u0026rsquo;s great for a first site. However, if you have something else in mind and want to use another theme, you can search for whatever you like in the Hugo themes repository.\nWe could download the zip and add it manually, but use the following command to get the latest version of Blowfish:\ngit submodule add -b main https://github.com/nunocoracao/blowfish.git themes/blowfish Blowfish has a complex configuration due to the different possibilities it offers. I do not recommend starting from scratch writing .toml files, or you will definitely break something. Copy the example files from themes/blowfish/config/_default/ to your config/_default/ folder. You might need to create the folders yourself.\nThen edit hugo.toml and change the baseURL:\nbaseURL = \u0026#34;https://docs.umarmohammad.xyz/\u0026#34; languageCode = \u0026#34;en\u0026#34; theme = \u0026#34;blowfish\u0026#34; title = \u0026#34;Umar Mohammad Docs\u0026#34; In my configuration, I put the URL https://docs.umarmohammad.xyz/, but you might not have your own domain. In that case, since you are going to use GitHub Pages, you should put github-username.github.io. That way, to access your site, someone will type github-username.github.io in their browser.\nNote github-username is your GitHub username, like mine is mr-umar.\nIf you have your own domain, I\u0026rsquo;ll also teach you how to configure the DNS in the next steps. For now, as a recommendation, don\u0026rsquo;t modify the theme or Hugo settings too much; you might screw it up 😵.\nLocal Testing # Now, before moving on, let\u0026rsquo;s test if our local repository works. In the website directory, run:\nhugo server Open http://localhost:1313 in your browser; if it works, congratulations. If not, read the error log in the terminal. At this point, it\u0026rsquo;s probably a stupid configuration error from touching the hugo.toml file.\nDeploying with GitHub Pages # Update: After digging a bit deeper into Cloudflare Pages, I found out it also supports deploying Hugo sites. More info here: official guide. If you don\u0026rsquo;t care about that, just carry on with this guide. :) Okay, now comes the fun part so anyone can access your site. First, from the terminal, make sure you are in your site\u0026rsquo;s directory if you weren\u0026rsquo;t already, and run:\n# Add everything to staging git add . # Commit git commit -m \u0026#34;First commit of my site with Hugo\u0026#34; # Push to the repo git branch -M main git push -u origin main Note If this is the first time you are pushing, it might ask you to authenticate. If it asks for a username and password, use a Personal Access Token. Your GitHub account password will NOT work.\nNow go to the repository on the web: GitHub → Settings → Pages. Next, you\u0026rsquo;ll see the Build and deployment option, and right below that, Source with a dropdown menu; we are interested in selecting the GitHub Actions option.\nNow, under the dropdown where you selected GitHub Actions, browse all workflows should appear. If you click it, search for hugo, hit Enter, and select Hugo by clicking Configure.\nNext, you will see a page very similar to the following one. The only thing you need to modify here is to go down to line 34, where it indicates HUGO_VERSION, and put the version you installed earlier on your local machine.\nIf you don\u0026rsquo;t do this step, you will very likely have problems later with the themes and settings you use. Once this is done, you must click on Commit changes.\nNote If you are going to use your own domain in the repo, under Settings → Pages, you must go to where it says Custom domain and enter your domain. Additionally, you must go to your domain\u0026rsquo;s DNS settings and create a CNAME record pointing to your-username.github.io. After making these adjustments, the next step you must take is to pull the changes from GitHub Actions to your local repo with the following command:\ngit pull Now, this step is important: if you are going to use a custom domain, you must execute the following with your domain:\necho \u0026#34;domain.com\u0026#34; \u0026gt; static/CNAME git add . git commit -m \u0026#34;CNAME record\u0026#34; git push origin main With this step, you just have to wait 2-3 minutes for GitHub Actions to launch your website on your custom domain or on your-username.github.io. Now you can access your website.\nFinally, to create content, I recommend opening the local repo folder with VSCode and installing the Front Matter CMS extension, which will simplify your life.\nAnd also visit the documentation for Hugo and Blowfish to learn more.\n","date":"14 February 2026","externalUrl":null,"permalink":"/posts/how-to-install-hugo/","section":"Posts","summary":"","title":"How to Install Hugo and Blowfish (without losing your mind)","type":"posts"},{"content":"","date":"14 February 2026","externalUrl":null,"permalink":"/tags/hugo/","section":"Tags","summary":"","title":"Hugo","type":"tags"},{"content":"","date":"14 February 2026","externalUrl":null,"permalink":"/tags/wordpress/","section":"Tags","summary":"","title":"WordPress","type":"tags"},{"content":"","externalUrl":null,"permalink":"/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":"Here you’ll find Machine Learning concepts as I learn them: key terms, fundamentals, summaries, and explanations I write to reuse in the future. Some posts may be incomplete or contain mistakes, so I’ll likely revise and update them over time.\n","externalUrl":null,"permalink":"/machine-learning/","section":"Machine Learning","summary":"","title":"Machine Learning","type":"machine-learning"},{"content":"","externalUrl":null,"permalink":"/series/","section":"Series","summary":"","title":"Series","type":"series"}]