{"id":163,"date":"2026-03-09T00:43:53","date_gmt":"2026-03-09T00:43:53","guid":{"rendered":"https:\/\/blog.rebalai.com\/en\/2026\/03\/09\/setting-up-github-actions-for-python-applications\/"},"modified":"2026-03-18T22:00:07","modified_gmt":"2026-03-18T22:00:07","slug":"setting-up-github-actions-for-python-applications","status":"publish","type":"post","link":"https:\/\/blog.rebalai.com\/en\/2026\/03\/09\/setting-up-github-actions-for-python-applications\/","title":{"rendered":"Setting Up GitHub Actions for Python: What the Docs Don&#8217;t Tell You"},"content":{"rendered":"<p>Three months ago, <a href=\"https:\/\/blog.rebalai.com\/en\/2026\/03\/08\/edge-computing-in-2026-why-developers-are-adopting\/\" title=\"Our Team\">our team<\/a>&#8217;s CI pipeline was a mess. We were running pytest on a five-person <a href=\"https:\/\/www.amazon.com\/s?k=python+programming+book&#038;tag=synsun0f-20\" title=\"Best Python Books on Amazon\" rel=\"nofollow sponsored\" target=\"_blank\">Python<\/a> project using a self-hosted Jenkins server that one of the founding engineers had set <a href=\"https:\/\/blog.rebalai.com\/en\/2026\/03\/05\/github-copilot-vs-cursor-vs-codeium-best-ai-coding\/\" title=\"Up in\">up in<\/a> 2019, and nobody really understood anymore. Build times were hitting 12 minutes, the server would randomly fail to clone repos, and we had a Slack channel called #ci-on-fire that was getting more traffic than #general.<\/p>\n<p>So I spent a weekend migrating everything to <a href=\"https:\/\/m.do.co\/c\/06956e5e2802\" title=\"Deploy with GitHub Actions on DigitalOcean\" rel=\"nofollow sponsored\" target=\"_blank\">GitHub Actions<\/a>. <a href=\"https:\/\/blog.rebalai.com\/en\/2026\/03\/08\/rag-deep-dive-chunking-strategies-vector-databases\/\" title=\"Two Weeks of\">Two weeks of<\/a> day-to-day use followed \u2014 plus a few Friday afternoon incidents I&#8217;d rather forget \u2014 and now we&#8217;re sitting at under three minutes per build, costing us exactly $0 on our open-source repos.<\/p>\n<p>This is <a href=\"https:\/\/blog.rebalai.com\/en\/2026\/03\/08\/rag-deep-dive-chunking-strategies-vector-databases\/\" title=\"What I Learned\">what I learned<\/a>, including the parts that would have saved me several hours if I&#8217;d known them upfront.<\/p>\n<h2>Your First Real Workflow (Not the Hello World Version)<\/h2>\n<p>The official docs will show you a 10-line YAML file that runs <code>pytest<\/code>. That&#8217;s fine for a toy project. For a real <a href=\"https:\/\/www.amazon.com\/s?k=python+programming+book&#038;tag=synsun0f-20\" title=\"Best Python Books on Amazon\" rel=\"nofollow sponsored\" target=\"_blank\">Python<\/a> application, you need to think about a few more things upfront: which <a href=\"https:\/\/www.amazon.com\/s?k=python+programming+book&#038;tag=synsun0f-20\" title=\"Best Python Books on Amazon\" rel=\"nofollow sponsored\" target=\"_blank\">Python<\/a> versions you support, how you manage dependencies, whether you&#8217;re linting before running tests or letting broken code waste CI minutes.<\/p>\n<p>Here&#8217;s the workflow <a href=\"https:\/\/blog.rebalai.com\/en\/2026\/03\/08\/github-copilot-alternatives-in-2026-cursor-codeium\/\" title=\"I Actually Use\">I actually use<\/a> for the <a href=\"https:\/\/m.do.co\/c\/06956e5e2802\" title=\"Deploy <a href=\"https:\/\/m.do.co\/c\/06956e5e2802\" title=\"Deploy FastAPI on DigitalOcean\" rel=\"nofollow sponsored\" target=\"_blank\">FastAPI<\/a> on <a href=\"https:\/\/m.do.co\/c\/06956e5e2802\" title=\"DigitalOcean Cloud Hosting \u2014 $200 credit for new users\" rel=\"nofollow sponsored\" target=\"_blank\">DigitalOcean<\/a>&#8221; rel=&#8221;nofollow sponsored&#8221; target=&#8221;_blank&#8221;>FastAPI<\/a> project we&#8217;ve been building:<\/p>\n<pre><code class=\"language-yaml\">name: CI\n\non:\n  push:\n    branches: [main, develop]\n  pull_request:\n    branches: [main]\n\njobs:\n  test:\n    runs-on: ubuntu-22.04\n    strategy:\n      matrix:\n        <a href=\"https:\/\/www.amazon.com\/s?k=python+programming+book&#038;tag=synsun0f-20\" title=\"Best Python Books on Amazon\" rel=\"nofollow sponsored\" target=\"_blank\">python<\/a>-version: [&quot;3.10&quot;, &quot;3.11&quot;, &quot;3.12&quot;]\n      fail-fast: false\n\n    steps:\n      - uses: actions\/checkout@v4\n\n      - name: Set up <a href=\"https:\/\/www.amazon.com\/s?k=python+programming+book&#038;tag=synsun0f-20\" title=\"Best Python Books on Amazon\" rel=\"nofollow sponsored\" target=\"_blank\">Python<\/a> ${{ matrix.<a href=\"https:\/\/www.amazon.com\/s?k=python+programming+book&#038;tag=synsun0f-20\" title=\"Best Python Books on Amazon\" rel=\"nofollow sponsored\" target=\"_blank\">python<\/a>-version }}\n        uses: actions\/setup-<a href=\"https:\/\/www.amazon.com\/s?k=python+programming+book&#038;tag=synsun0f-20\" title=\"Best Python Books on Amazon\" rel=\"nofollow sponsored\" target=\"_blank\">python<\/a>@v5\n        with:\n          python-version: ${{ matrix.python-version }}\n\n      - name: Cache pip dependencies\n        uses: actions\/cache@v4\n        with:\n          path: ~\/.cache\/pip\n          key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('**\/requirements*.txt') }}\n          restore-keys: |\n            ${{ runner.os }}-pip-${{ matrix.python-version }}-\n            ${{ runner.os }}-pip-\n\n      - name: Install dependencies\n        run: |\n          python -m pip install --upgrade pip\n          pip install -r requirements.txt\n          pip install -r requirements-dev.txt\n\n      - name: Lint with ruff\n        run: ruff check .\n\n      - name: Run tests with coverage\n        run: pytest --cov=. --cov-report=xml --cov-fail-under=80\n\n      - name: Upload coverage to Codecov\n        if: matrix.python-version == '3.11'\n        uses: codecov\/codecov-action@v4\n        with:\n          token: ${{ secrets.CODECOV_TOKEN }}\n<\/code><\/pre>\n<p>Two things I want to explain explicitly. The <code>fail-fast: false<\/code> on the matrix \u2014 without that, if Python 3.10 fails, GitHub immediately cancels the 3.11 and 3.12 jobs. Sometimes that&#8217;s what you want. More often, you want to see if 3.12 passes cleanly while 3.10 has an unrelated issue. I leave it false and see all results.<\/p>\n<p>The <code>ubuntu-22.04<\/code> pin is intentional. Leaving <a href=\"https:\/\/blog.rebalai.com\/en\/2026\/03\/09\/serverless-vs-containers-in-2026-a-practical-decis\/\" title=\"It as\">it as<\/a> <code>ubuntu-latest<\/code> means your workflow can silently break when GitHub bumps the default to a newer Ubuntu version. This happened to us \u2014 a transitive dependency wasn&#8217;t compatible with ubuntu-24.04 yet, and we spent half a morning convinced there was a code issue before someone noticed the runner change <a href=\"https:\/\/blog.rebalai.com\/en\/2026\/03\/08\/rag-deep-dive-chunking-strategies-vector-databases\/\" title=\"in the\">in the<\/a> logs. Now I pin and update deliberately, on my schedule.<\/p>\n<p>Linting before tests is also intentional. <code>ruff<\/code> runs in under two seconds on our codebase. There&#8217;s no reason to waste three minutes running pytest on code with obvious import errors.<\/p>\n<h2>Caching: The Part <a href=\"https:\/\/blog.rebalai.com\/en\/2026\/03\/09\/typescript-5x-in-2026-features-that-actually-matte\/\" title=\"That Actually\">That Actually<\/a> Matters for Build Speed<\/h2>\n<p>Here is the thing: most tutorials either skip caching entirely or get the cache key wrong, leaving you with either constant cache misses or stale dependencies causing confusing failures.<\/p>\n<p>The pattern I use \u2014 <code>runner.os + python-version + hash of requirements files<\/code> \u2014 hits the right balance. If your requirements don&#8217;t change, you get a cache hit. The <code>restore-keys<\/code> fallback means if you add a new package, you start from the previous cache instead of from scratch, shaving a minute or two off even a miss.<\/p>\n<p>One thing I noticed when I first set this up: I was caching <code>~\/.cache\/pip<\/code> but not seeing the improvement I expected. Turned out my <code>requirements.txt<\/code> wasn&#8217;t pinned \u2014 it had things like <code>fastapi&gt;=0.100.0<\/code>, so the hash was stable but pip was still reaching out to PyPI to check for newer versions on every run. After switching to pinned requirements generated with <code>pip-compile<\/code> from pip-tools, cache hits went from saving about 20 seconds to saving nearly three minutes per matrix leg. That compounds fast when you&#8217;re running three Python versions on every PR.<\/p>\n<p>If you&#8217;re on Poetry, the caching setup is different. You want to cache the Poetry virtualenv, and you need <code>virtualenvs.in-project = true<\/code> so the path is predictable:<\/p>\n<pre><code class=\"language-yaml\">- name: Cache Poetry virtualenv\n  uses: actions\/cache@v4\n  with:\n    path: .venv\n    key: ${{ runner.os }}-venv-${{ matrix.python-version }}-${{ hashFiles('poetry.lock') }}\n<\/code><\/pre>\n<p>Using <code>poetry.lock<\/code> as your hash source is correct \u2014 it&#8217;s the exact dependency snapshot, and it changes whenever anything <a href=\"https:\/\/blog.rebalai.com\/en\/2026\/03\/08\/rag-deep-dive-chunking-strategies-vector-databases\/\" title=\"in the\">in the<\/a> tree changes, including transitive dependencies.<\/p>\n<h2>Secrets, Databases, and the Configuration <a href=\"https:\/\/blog.rebalai.com\/en\/2026\/03\/09\/typescript-5x-in-2026-features-that-actually-matte\/\" title=\"That Actually\">That Actually<\/a> Works<\/h2>\n<p>Managing secrets in <a href=\"https:\/\/m.do.co\/c\/06956e5e2802\" title=\"Deploy with <a href=\"https:\/\/m.do.co\/c\/06956e5e2802\" title=\"Deploy with GitHub Actions on DigitalOcean\" rel=\"nofollow sponsored\" target=\"_blank\">GitHub Actions<\/a> on <a href=\"https:\/\/m.do.co\/c\/06956e5e2802\" title=\"DigitalOcean Cloud Hosting \u2014 $200 credit for new users\" rel=\"nofollow sponsored\" target=\"_blank\">DigitalOcean<\/a>&#8221; rel=&#8221;nofollow sponsored&#8221; target=&#8221;_blank&#8221;>GitHub Actions<\/a> is well designed. You add them in Settings \u2192 Secrets and variables \u2192 Actions, reference them as <code>${{ secrets.MY_SECRET }}<\/code>, and they&#8217;re masked in logs. That part is fine.<\/p>\n<p>What gets messier is service dependencies. If your test suite hits a real database \u2014 and it probably should for integration tests \u2014 you need to spin one up. <a href=\"https:\/\/m.do.co\/c\/06956e5e2802\" title=\"Deploy with <a href=\"https:\/\/m.do.co\/c\/06956e5e2802\" title=\"Deploy with GitHub Actions on DigitalOcean\" rel=\"nofollow sponsored\" target=\"_blank\">GitHub Actions<\/a> on <a href=\"https:\/\/m.do.co\/c\/06956e5e2802\" title=\"DigitalOcean Cloud Hosting \u2014 $200 credit for new users\" rel=\"nofollow sponsored\" target=\"_blank\">DigitalOcean<\/a>&#8221; rel=&#8221;nofollow sponsored&#8221; target=&#8221;_blank&#8221;>GitHub Actions<\/a> has first-class support for service containers:<\/p>\n<pre><code class=\"language-yaml\">services:\n  postgres:\n    image: postgres:16\n    env:\n      POSTGRES_PASSWORD: postgres\n      POSTGRES_DB: testdb\n    ports:\n      - 5432:5432\n    options: &gt;-\n      --health-cmd pg_isready\n      --health-interval 10s\n      --health-timeout 5s\n      --health-retries 5\n<\/code><\/pre>\n<p>The health check options are critical. Without them, your test step starts before Postgres <a href=\"https:\/\/blog.rebalai.com\/en\/2026\/03\/05\/copilot-vs-cursor-vs-codeium\/\" title=\"Is Actually\">is actually<\/a> ready to accept connections, and you get flaky tests you&#8217;ll spend hours debugging \u2014 convinced it&#8217;s a race condition in your own code. <a href=\"https:\/\/blog.rebalai.com\/en\/2026\/03\/08\/rag-deep-dive-chunking-strategies-vector-databases\/\" title=\"I Learned\">I learned<\/a> this the slow way. Three days of &#8220;sometimes fails, can&#8217;t reproduce locally&#8221; before I noticed the missing health check <a href=\"https:\/\/blog.rebalai.com\/en\/2026\/03\/08\/rag-deep-dive-chunking-strategies-vector-databases\/\" title=\"in the\">in the<\/a> workflow file.<\/p>\n<p>One gotcha with secrets: they&#8217;re not passed to workflows triggered by pull requests from forks. This is a deliberate security decision \u2014 if fork PRs could access your secrets, a malicious contributor could exfiltrate them through a workflow change. For open-source projects where you want CI on fork PRs, you either have a maintainer re-run the workflow from the base repository, or you structure your test suite so the basic tests don&#8217;t need real credentials at all. We went with the latter \u2014 mock external services in unit tests, real services only in integration tests that run post-merge.<\/p>\n<h2>The Mistake That Cost Me a Friday Afternoon<\/h2>\n<p>Right, so \u2014 I&#8217;m going to be honest about something embarrassing.<\/p>\n<p>We had a deployment workflow set up. Push to <code>main<\/code>, tests pass, <a href=\"https:\/\/m.do.co\/c\/06956e5e2802\" title=\"Deploy on DigitalOcean Cloud\" rel=\"nofollow sponsored\" target=\"_blank\">deploy<\/a> to staging. I pushed at 4:30pm on a Friday because I was impatient to see the feature live. The tests passed, the deployment step started, and then it failed trying to verify the host key <a href=\"https:\/\/blog.rebalai.com\/en\/2026\/03\/08\/edge-computing-in-2026-why-developers-are-adopting\/\" title=\"for Our\">for our<\/a> server. I&#8217;d set up strict host key checking (correct behavior), but <a href=\"https:\/\/blog.rebalai.com\/en\/2026\/03\/08\/rag-deep-dive-chunking-strategies-vector-databases\/\" title=\"Two Weeks\">two weeks<\/a> earlier we&#8217;d migrated to a new server and forgotten to update the <code>known_hosts<\/code> secret.<\/p>\n<p>Not catastrophic. Just: re-add the correct known_hosts value, commit, push, watch the workflow, wait another four minutes. By the time everything deployed it was past 6pm.<\/p>\n<p>The lesson isn&#8217;t &#8220;don&#8217;t push on Fridays.&#8221; It&#8217;s that deployment workflows should be separate from test workflows, and you should have a <code>workflow_dispatch<\/code> trigger for manual runs:<\/p>\n<pre><code class=\"language-yaml\">on:\n  push:\n    branches: [main]\n  workflow_dispatch:\n    inputs:\n      environment:\n        description: 'Target environment'\n        required: true\n        default: 'staging'\n        type: choice\n        options: [staging, <a href=\"https:\/\/m.do.co\/c\/06956e5e2802\" title=\"DigitalOcean for <a href=\"https:\/\/m.do.co\/c\/06956e5e2802\" title=\"DigitalOcean <a href=\"https:\/\/blog.rebalai.com\/en\/2026\/03\/04\/fine-tuning-vs-rag-when-to-use-each-approach-for-<a href=\"https:\/\/m.do.co\/c\/06956e5e2802\" title=\"DigitalOcean for <a href=\"https:\/\/m.do.co\/c\/06956e5e2802\" title=\"DigitalOcean for Production Workloads\" rel=\"nofollow sponsored\" target=\"_blank\">Production<\/a> Workloads\" rel=\"nofollow sponsored\" target=\"_blank\">production<\/a>-llms\/\" title=\"for Production\">for Production<\/a> Workloads\" rel=\"nofollow sponsored\" target=\"_blank\">Production<\/a> Workloads\" rel=\"nofollow sponsored\" target=\"_blank\">production<\/a>]\n<\/code><\/pre>\n<p><code>workflow_dispatch<\/code> is genuinely underused. It turns your workflow into something you can trigger manually from the GitHub UI with optional inputs \u2014 useful for deployments, one-off data migrations, scheduled reports, anything where you want the CI machinery without tying it to a specific commit.<\/p>\n<h2>Reusable Workflows and Avoiding YAML Sprawl<\/h2>\n<p>Once you have more than two or three workflows, duplication becomes a real problem. The Python setup steps are the same, the caching logic is the same, the linting is the same. <a href=\"https:\/\/m.do.co\/c\/06956e5e2802\" title=\"Deploy with <a href=\"https:\/\/m.do.co\/c\/06956e5e2802\" title=\"Deploy with GitHub Actions on DigitalOcean\" rel=\"nofollow sponsored\" target=\"_blank\">GitHub Actions<\/a> on DigitalOcean&#8221; rel=&#8221;nofollow sponsored&#8221; target=&#8221;_blank&#8221;>GitHub Actions<\/a> has two mechanisms for this: composite actions and reusable workflows.<\/p>\n<p>Composite actions live in your repo under <code>.github\/actions\/<\/code>. A <code>setup-python-env<\/code> composite action that handles setup, caching, and dependency installation means your workflow files shrink from 50 lines to 15, and when you update the caching key strategy, you update it once. I&#8217;m not sure this scales cleanly beyond a team of ten working across multiple repos \u2014 at some point you probably want a dedicated shared-actions repository. <a href=\"https:\/\/blog.rebalai.com\/en\/2026\/03\/08\/edge-computing-in-2026-why-developers-are-adopting\/\" title=\"for Our\">For our<\/a> five-person monorepo, keeping composite actions <a href=\"https:\/\/blog.rebalai.com\/en\/2026\/03\/08\/rag-deep-dive-chunking-strategies-vector-databases\/\" title=\"in the\">in the<\/a> same repo works fine.<\/p>\n<p>Reusable workflows are different \u2014 they let you call an entire <code>.yml<\/code> file as a job from another workflow. They require an explicit <code>workflow_call<\/code> trigger and are particularly useful when you have integration tests that three different triggering workflows all need to run. The syntax is <code>uses: .\/.github\/workflows\/integration-tests.yml<\/code> for same-repo calls, or <code>uses: org\/repo\/.github\/workflows\/file.yml@main<\/code> for cross-repo.<\/p>\n<p>Honestly, the specific pattern matters less than being consistent. Pick one approach before you have five slightly different linting configurations across five workflow files.<\/p>\n<h2>What I&#8217;d Actually Recommend<\/h2>\n<p>Starting from scratch? Use the matrix workflow above, pin your Ubuntu version, set up dependency caching with pinned requirements, and add service containers for whatever databases your tests need. That covers the majority of what you&#8217;ll need day-to-day.<\/p>\n<p>On dependency management: <code>pip-compile<\/code> with pinned requirements or Poetry with a lockfile. Don&#8217;t mix strategies mid-project. The reproducibility and caching benefits compound.<\/p>\n<p>Deployments should be in a separate workflow from tests. Use <code>workflow_run<\/code> to trigger deployment after the test workflow completes, and <code>workflow_dispatch<\/code> for any case where you want manual control over when something runs.<\/p>\n<p>Self-hosted runners <a href=\"https:\/\/blog.rebalai.com\/en\/2026\/03\/04\/cursor-vs-github-copilot-vs-continue-ai-code-editor-showdown-2026\/\" title=\"vs GitHub\">vs GitHub<\/a>-hosted: GitHub-hosted is good enough for almost all Python projects. The 2-core, 7GB RAM machines handle pytest suites <a href=\"https:\/\/blog.rebalai.com\/en\/2026\/03\/08\/rag-deep-dive-chunking-strategies-vector-databases\/\" title=\"in the\">in the<\/a> thousands without breaking a sweat. If you&#8217;re doing <a href=\"https:\/\/www.amazon.com\/s?k=GPU+for+deep+learning&#038;tag=synsun0f-20\" title=\"Best GPUs for AI and <a href=\"https:\/\/www.amazon.com\/s?k=deep+learning+book&#038;tag=synsun0f-20\" title=\"Best <a href=\"https:\/\/www.amazon.com\/s?k=deep+learning+book&#038;tag=synsun0f-20\" title=\"Best <a href=\"https:\/\/www.amazon.com\/s?k=deep+learning+book&#038;tag=synsun0f-20\" title=\"Best <a href=\"https:\/\/www.amazon.com\/s?k=deep+learning+book&#038;tag=synsun0f-20\" title=\"Best <a href=\"https:\/\/www.amazon.com\/s?k=deep+learning+book&#038;tag=synsun0f-20\" title=\"Best <a href=\"https:\/\/www.amazon.com\/s?k=deep+learning+book&#038;tag=synsun0f-20\" title=\"Best <a href=\"https:\/\/www.amazon.com\/s?k=deep+learning+book&#038;tag=synsun0f-20\" title=\"Best <a href=\"https:\/\/www.amazon.com\/s?k=deep+learning+book&#038;tag=synsun0f-20\" title=\"Best Deep Learning Books on Amazon\" rel=\"nofollow sponsored\" target=\"_blank\">Deep Learning<\/a> Books on Amazon&#8221; rel=&#8221;nofollow sponsored&#8221; target=&#8221;_blank&#8221;>Deep Learning<\/a> Books on Amazon&#8221; rel=&#8221;nofollow sponsored&#8221; target=&#8221;_blank&#8221;>Deep Learning<\/a> Books on Amazon&#8221; rel=&#8221;nofollow sponsored&#8221; target=&#8221;_blank&#8221;>Deep Learning<\/a> Books on Amazon&#8221; rel=&#8221;nofollow sponsored&#8221; target=&#8221;_blank&#8221;>Deep Learning<\/a> Books on Amazon&#8221; rel=&#8221;nofollow sponsored&#8221; target=&#8221;_blank&#8221;>Deep Learning<\/a> Books on Amazon&#8221; rel=&#8221;nofollow sponsored&#8221; target=&#8221;_blank&#8221;>Deep Learning<\/a> Books on Amazon&#8221; rel=&#8221;nofollow sponsored&#8221; target=&#8221;_blank&#8221;>Deep Learning<\/a> on Amazon&#8221; rel=&#8221;nofollow sponsored&#8221; target=&#8221;_blank&#8221;>GPU<\/a> training or heavy native compilation, then yes, self-hosted <a href=\"https:\/\/blog.rebalai.com\/en\/2026\/03\/09\/webassembly-in-2026-where-it-actually-makes-sense\/\" title=\"Makes Sense\">makes sense<\/a>. Otherwise the operational overhead isn&#8217;t <a href=\"https:\/\/blog.rebalai.com\/en\/2026\/03\/05\/copilot-vs-cursor-vs-codeium\/\" title=\"Worth It\">worth it<\/a> \u2014 you&#8217;d spend more time maintaining runner infrastructure than you&#8217;d save in CI minutes. That&#8217;s the same trap we were in with Jenkins, just with shinier branding.<\/p>\n<p>The best thing about GitHub Actions, honestly, is that it&#8217;s just YAML with predictable behavior. When something breaks, I can usually figure out why within five minutes of reading the logs. That&#8217;s more than I could say for the Jenkins setup we came from, where debugging often meant SSHing into a server and praying.<\/p>\n<p><!-- Reviewed: 2026-03-09 | Status: ready_to_publish | Changes: expanded meta description to 153 chars, reworded \"A few things worth calling out\" opener, broke up parallel 'For X:' recommendation pattern, varied paragraph lengths in secrets\/deploy sections, sharpened fork secrets explanation with concrete solution, removed \"compounds quickly\" redundancy --><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Three months ago, our team \u2019s CI pipeline was a mess.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"categories":[1],"tags":[],"class_list":["post-163","post","type-post","status-publish","format-standard","hentry","category-general"],"_links":{"self":[{"href":"https:\/\/blog.rebalai.com\/en\/wp-json\/wp\/v2\/posts\/163","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.rebalai.com\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.rebalai.com\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.rebalai.com\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.rebalai.com\/en\/wp-json\/wp\/v2\/comments?post=163"}],"version-history":[{"count":20,"href":"https:\/\/blog.rebalai.com\/en\/wp-json\/wp\/v2\/posts\/163\/revisions"}],"predecessor-version":[{"id":509,"href":"https:\/\/blog.rebalai.com\/en\/wp-json\/wp\/v2\/posts\/163\/revisions\/509"}],"wp:attachment":[{"href":"https:\/\/blog.rebalai.com\/en\/wp-json\/wp\/v2\/media?parent=163"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.rebalai.com\/en\/wp-json\/wp\/v2\/categories?post=163"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.rebalai.com\/en\/wp-json\/wp\/v2\/tags?post=163"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}