EN

Docker and infrastructure as code

Most sites and applications run on a server that was set up by hand, once, by someone who has since moved on. It works until it does not, and then nobody can reproduce it. We replace that with an environment written down in files: buildable, testable and reversible.

services:  app:    image: registry.example.com/app:1.4.0    restart: unless-stopped    depends_on: [db, cache]  db:    image: mariadb:11    volumes: ['db-data:/var/lib/mysql']volumes:  db-data:

Services

What we do

The same thing runs on the developer's laptop, on staging and in production: not something similar, literally the same image.

The image comes from a single build. If it is good on staging it is good in production, because what goes out is not a similarly assembled copy but the same package: the same PHP version, the same extensions, the same dependencies, the same server configuration.

FROM php:8.4-fpm-alpine AS baseRUN docker-php-ext-install pdo_mysql opcacheCOPY --from=composer:2 /usr/bin/composer /usr/bin/composerCOPY composer.json composer.lock ./RUN composer install --no-dev --optimize-autoloaderCOPY . /appCMD ["php-fpm"]

The outcome

Why it is worth it

That one property removes an entire category of bugs: the ones that only appear on the live server, at the worst possible time, and cannot be reproduced anywhere else.

Deployment stops being an event. It becomes a routine step that runs during working hours instead of on a Friday night, and if a release turns out to be wrong, going back is one command, not an evening of manual repair. Backups are tested by restoring them, not by trusting them.

Predictability then stops being a matter of discipline. The state of the server lives in the repository rather than in somebody's memory, each step was written down once, and a machine performs it the hundredth time exactly as it did the first.

Deployment before and after

In most places deployment is a sequence of steps one person knows by heart. While that person is available, it works. When they are on leave, the release waits; when a step gets skipped, production is where you find out.

The pipeline performs the same sequence, only the same way every time, and it stops if something is off. The question stops being who has time to deploy and becomes whether the work is finished. From the client's side this is the difference that shows up first: a fix ships when it is ready, not when somebody gets round to it. And if it turns out to be wrong, going back is one step.

.gitlab-ci.yml

#1482 main a3f19c2 4m 12s
  1. build passed 1m 12s

    The image is produced from the repository.

    • docker build 1m 04s
    • push registry 8s
  2. test passed 2m 08s

    Every check runs, and one failure stops the run.

    • phpunit 1m 31s
    • phpstan analyse 29s
    • pint --test 8s
  3. deploy passed 46s

    Staging receives the very same image.

    • compose pull 31s
    • migrate --force 15s
  4. release running 6s

    The pipeline puts it live too, on a tag.

    • tag v1.4.0 2s
    • rollback: previous image 4s
All four stages run on machines. A tag triggers going live, and the same sequence runs.

The handover

What exactly we take over

“Taking over operations” says nothing on its own, so here it is item by item: what moves onto our desk. You do not need all of it at once. Most engagements start with the first two, and the rest follows.

  1. Survey

    We go through what actually runs today: which services, which versions, what is stored where, who has access. At the end there is a written description that did not exist anywhere before.

  2. Containerisation

    The application and its environment go into a single versioned image. We do not rewrite the application for it, and the move happens in stages, with a rollback path at every step.

  3. CI/CD

    Tests, static analysis and image build on every commit, deployment on a tag or a button. Who is allowed to deploy is something you grant, not something that follows from holding the server password.

  4. Backups

    Backups of the files and the database, and more importantly a restore drill. A backup that has never been restored is not a backup, it is a hope.

  5. Monitoring

    We measure whether it is up, how long it takes to answer, how much space is left, when the certificate expires. Alerts go where somebody sees them, not to a mailbox nobody reads.

  6. Access

    Who can log in to the server, who can deploy, where the passwords and keys are kept. In most handovers this is exactly the part that had never been written down.

#!/bin/shset -eu# A backup on its own is hope. The restore test is the evidence.docker compose exec -T db mysqldump app > /backup/app-$(date +%F).sqlrestic backup /backup /var/www/storage# Once a week: the latest backup restored into a throwaway database.restic restore latest --target /tmp/verifydocker compose exec -T db-verify mysql verify < /tmp/verify/app-*.sql
The second half restores the backup: the test is what makes it a backup.

If you have your own team

We do not take work away. Your team currently spends part of its time keeping the environment alive: versions, permissions, certificates, manual deployment. That is exactly the part that moves into files.

The work does not disappear, it changes shape. What used to be a login to the server becomes a reviewable change: it is visible who changed what and why, and it can be reverted. The handover is not a document either. Your people write the second and third change themselves, with us alongside.

If you already have an operations provider

They do not have to be replaced. Often the right answer is that we describe today's environment in code and they run it from there, with the same tools. The point of a container is precisely that it is not tied to whoever hosts it.

If you are under contract with your current provider, that contract does not have to end for the environment to be written down. And if the survey concludes that everything at your end is in order, we will say that too. That is not a bad outcome.

When something breaks

What happens in an incident

This is the question that actually matters before a contract is signed, and the one most proposals leave out:

  • A bad release went out: going back to the previous image is one step, not one night.
  • Data was lost: we restore from backup, and we know it works because it has been tried.
  • The server is gone: the environment is in files, so it can be rebuilt rather than reassembled from memory.
  • You do not know who to call: an agreed contact and response time, with a name on it, not a general mailbox.

The response time belongs in the contract, not on this page: it depends on what we operate and what you take on for it. What can be written here: the conversation after an incident is about why it cannot happen again, and most of that answer ends up in code.

We measure availability continuously, and we show the measured figure. In the operations contract it is a committed number, not a promise, and the same background stands behind the systems that run intrapp.io.

References

Systems we built and also run

A few examples from our work. We developed each of them and we hold the runtime as well, so they run in containers, on infrastructure described in code, with automated releases.

We ship our own work this way

This site runs in a container built by the same pipeline we set up for clients:

intrapp.io, the business operating system behind our custom development work, runs the same way, with real companies' daily operations on it.

That is the reference. We are not describing a method we read about; we are describing the one we depend on ourselves.

deploy:  stage: deploy  image: docker:27  script:    - docker compose pull    - docker compose up -d --wait  rules:    - if: $CI_COMMIT_TAG      when: manual
Going live only starts on a tag: when a release happens is decided by when the tag is pushed.
The Telefonszám tudakozó home page, with the search box in the middle and the most common call types below it.
Telefonszám tudakozó runs on our infrastructure: in containers, on an environment built from code, with data going back to 2003 and tens of thousands of searches a day.

Tell us what you are running today

A short description of the current setup is enough: what the application is, where it runs, and what hurts. We will tell you what containerising it would involve and whether it is worth doing.

Get in touch