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:
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.
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.
-
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.
-
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.
-
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.
-
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.
-
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.
-
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
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.
We ship our own work this way
This site runs in a container built by the same pipeline we set up for clients:
- tests on every commit
- an image built from the repository
- a deployment that can be rolled back
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
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.